mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-31 08:46:58 +02:00
test: Refactor node creator tests to Playwright (#19808)
This commit is contained in:
parent
b6b3b7007e
commit
c0b553e464
|
|
@ -1,576 +0,0 @@
|
|||
import { clickGetBackToCanvas } from '../../composables/ndv';
|
||||
import {
|
||||
addNodeToCanvas,
|
||||
addRetrieverNodeToParent,
|
||||
addVectorStoreNodeToParent,
|
||||
addVectorStoreToolToParent,
|
||||
getNodeCreatorItems,
|
||||
} from '../../composables/workflow';
|
||||
import { AGENT_NODE_NAME, IF_NODE_NAME, MANUAL_CHAT_TRIGGER_NODE_NAME } from '../../constants';
|
||||
import { NodeCreator } from '../../pages/features/node-creator';
|
||||
import { NDV } from '../../pages/ndv';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../../pages/workflow';
|
||||
import { getVisibleSelect } from '../../utils';
|
||||
|
||||
const nodeCreatorFeature = new NodeCreator();
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
const NDVModal = new NDV();
|
||||
|
||||
describe('Node Creator', () => {
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.visit();
|
||||
});
|
||||
|
||||
it('should open node creator on trigger tab if no trigger is on canvas', () => {
|
||||
nodeCreatorFeature.getters.canvasAddButton().click();
|
||||
|
||||
nodeCreatorFeature.getters
|
||||
.nodeCreator()
|
||||
.contains('What triggers this workflow?')
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
it('should navigate subcategory', () => {
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
nodeCreatorFeature.getters.getCreatorItem('On app event').click();
|
||||
nodeCreatorFeature.getters.activeSubcategory().should('have.text', 'On app event');
|
||||
// Go back
|
||||
nodeCreatorFeature.getters.activeSubcategory().find('button').click();
|
||||
nodeCreatorFeature.getters.activeSubcategory().should('not.have.text', 'On app event');
|
||||
});
|
||||
|
||||
it('should search for nodes', () => {
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').type('manual');
|
||||
nodeCreatorFeature.getters.creatorItem().should('have.length', 1);
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('manual123');
|
||||
nodeCreatorFeature.getters.creatorItem().should('have.length', 0);
|
||||
nodeCreatorFeature.getters
|
||||
.noResults()
|
||||
.should('exist')
|
||||
.should('contain.text', "We didn't make that... yet");
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('edit image');
|
||||
nodeCreatorFeature.getters.creatorItem().should('have.length', 1);
|
||||
|
||||
nodeCreatorFeature.getters
|
||||
.searchBar()
|
||||
.find('input')
|
||||
.clear()
|
||||
.type('this node totally does not exist');
|
||||
nodeCreatorFeature.getters.creatorItem().should('have.length', 0);
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear();
|
||||
nodeCreatorFeature.getters.getCreatorItem('On app event').click();
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('edit image');
|
||||
nodeCreatorFeature.getters.getCategoryItem('Results in other categories').should('exist');
|
||||
nodeCreatorFeature.getters.creatorItem().should('have.length', 1);
|
||||
nodeCreatorFeature.getters.getCreatorItem('Edit Image').should('exist');
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('edit image123123');
|
||||
nodeCreatorFeature.getters.creatorItem().should('have.length', 0);
|
||||
});
|
||||
|
||||
it('should check correct view panels', () => {
|
||||
nodeCreatorFeature.getters.canvasAddButton().click();
|
||||
WorkflowPage.actions.addNodeToCanvas('Manual', false);
|
||||
|
||||
nodeCreatorFeature.getters.canvasAddButton().should('not.exist');
|
||||
nodeCreatorFeature.getters.nodeCreator().should('not.exist');
|
||||
// TODO: Replace once we have canvas feature utils
|
||||
cy.get('div').contains('Add first step').should('not.exist');
|
||||
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
nodeCreatorFeature.getters.nodeCreator().contains('What happens next?').should('be.visible');
|
||||
|
||||
nodeCreatorFeature.getters.getCreatorItem('Add another trigger').click();
|
||||
nodeCreatorFeature.getters
|
||||
.nodeCreator()
|
||||
.contains('What triggers this workflow?')
|
||||
.should('be.visible');
|
||||
nodeCreatorFeature.getters.activeSubcategory().find('button').should('exist');
|
||||
nodeCreatorFeature.getters.activeSubcategory().find('button').click();
|
||||
nodeCreatorFeature.getters.nodeCreator().contains('What happens next?').should('be.visible');
|
||||
});
|
||||
|
||||
it('should add node to canvas from actions panel', () => {
|
||||
const editImageNode = 'Edit Image';
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type(editImageNode);
|
||||
nodeCreatorFeature.getters.getCreatorItem(editImageNode).click();
|
||||
nodeCreatorFeature.getters.activeSubcategory().should('have.text', editImageNode);
|
||||
nodeCreatorFeature.getters.getCreatorItem('Crop Image').click();
|
||||
NDVModal.getters.parameterInput('operation').find('input').should('have.value', 'Crop');
|
||||
});
|
||||
|
||||
it('should search through actions and confirm added action', () => {
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('ftp');
|
||||
nodeCreatorFeature.getters.searchBar().find('input').type('{rightarrow}');
|
||||
nodeCreatorFeature.getters.activeSubcategory().should('have.text', 'FTP');
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('file');
|
||||
// The 1st trigger is selected, up 1x to the collapsable header, up 2x to the last action (rename)
|
||||
nodeCreatorFeature.getters.searchBar().find('input').type('{uparrow}{uparrow}{rightarrow}');
|
||||
NDVModal.getters.parameterInput('operation').find('input').should('have.value', 'Rename');
|
||||
});
|
||||
|
||||
it('should not show actions for single action nodes', () => {
|
||||
const singleActionNodes = [
|
||||
'DHL',
|
||||
'Edit Fields',
|
||||
'LingvaNex',
|
||||
'Mailcheck',
|
||||
'MSG91',
|
||||
'OpenThesaurus',
|
||||
'Spontit',
|
||||
'Vonage',
|
||||
'Toggl Trigger',
|
||||
];
|
||||
const doubleActionNode = 'OpenWeatherMap';
|
||||
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
singleActionNodes.forEach((node) => {
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type(node);
|
||||
nodeCreatorFeature.getters
|
||||
.getCreatorItem(node)
|
||||
.find('button[class*="panelIcon"]')
|
||||
.should('not.exist');
|
||||
});
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type(doubleActionNode);
|
||||
nodeCreatorFeature.getters.getCreatorItem(doubleActionNode).click();
|
||||
nodeCreatorFeature.getters.creatorItem().should('have.length', 4);
|
||||
});
|
||||
|
||||
it('should have "Actions" section collapsed when opening actions view from Trigger root view', () => {
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('ActiveCampaign');
|
||||
nodeCreatorFeature.getters.getCreatorItem('ActiveCampaign').click();
|
||||
nodeCreatorFeature.getters.getCategoryItem('Actions').should('exist');
|
||||
nodeCreatorFeature.getters.getCategoryItem('Triggers').should('exist');
|
||||
|
||||
nodeCreatorFeature.getters
|
||||
.getCategoryItem('Triggers')
|
||||
.parent()
|
||||
.should('have.attr', 'data-category-collapsed', 'false');
|
||||
nodeCreatorFeature.getters
|
||||
.getCategoryItem('Actions')
|
||||
.parent()
|
||||
.should('have.attr', 'data-category-collapsed', 'true');
|
||||
nodeCreatorFeature.getters.getCategoryItem('Actions').click();
|
||||
nodeCreatorFeature.getters
|
||||
.getCategoryItem('Actions')
|
||||
.parent()
|
||||
.should('have.attr', 'data-category-collapsed', 'false');
|
||||
});
|
||||
|
||||
it('should have "Triggers" section collapsed when opening actions view from Regular root view', () => {
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
nodeCreatorFeature.getters.getCreatorItem('Trigger manually').click();
|
||||
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n');
|
||||
nodeCreatorFeature.getters.getCreatorItem('n8n').click();
|
||||
|
||||
nodeCreatorFeature.getters
|
||||
.getCategoryItem('Actions')
|
||||
.parent()
|
||||
.should('have.attr', 'data-category-collapsed', 'false');
|
||||
nodeCreatorFeature.getters.getCategoryItem('Actions').click();
|
||||
nodeCreatorFeature.getters
|
||||
.getCategoryItem('Actions')
|
||||
.parent()
|
||||
.should('have.attr', 'data-category-collapsed', 'true');
|
||||
nodeCreatorFeature.getters
|
||||
.getCategoryItem('Triggers')
|
||||
.parent()
|
||||
.should('have.attr', 'data-category-collapsed', 'true');
|
||||
nodeCreatorFeature.getters.getCategoryItem('Triggers').click();
|
||||
nodeCreatorFeature.getters
|
||||
.getCategoryItem('Triggers')
|
||||
.parent()
|
||||
.should('have.attr', 'data-category-collapsed', 'false');
|
||||
});
|
||||
|
||||
it('should show callout and two suggested nodes if node has no trigger actions', () => {
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
nodeCreatorFeature.getters
|
||||
.searchBar()
|
||||
.find('input')
|
||||
.clear()
|
||||
.type('Customer Datastore (n8n training)');
|
||||
nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click();
|
||||
|
||||
cy.getByTestId('actions-panel-no-triggers-callout').should('be.visible');
|
||||
nodeCreatorFeature.getters.getCreatorItem('On a Schedule').should('be.visible');
|
||||
nodeCreatorFeature.getters.getCreatorItem('On a Webhook call').should('be.visible');
|
||||
});
|
||||
|
||||
it('should show intro callout if user has not made a production execution', () => {
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
nodeCreatorFeature.getters
|
||||
.searchBar()
|
||||
.find('input')
|
||||
.clear()
|
||||
.type('Customer Datastore (n8n training)');
|
||||
nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click();
|
||||
|
||||
cy.getByTestId('actions-panel-activation-callout').should('be.visible');
|
||||
nodeCreatorFeature.getters.activeSubcategory().find('button').click();
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear();
|
||||
|
||||
nodeCreatorFeature.getters.getCreatorItem('On a schedule').click();
|
||||
|
||||
// Setup 1s interval execution
|
||||
cy.getByTestId('parameter-input-field').click();
|
||||
getVisibleSelect().find('.option-headline').contains('Seconds').click();
|
||||
cy.getByTestId('parameter-input-secondsInterval').clear().type('1');
|
||||
|
||||
NDVModal.actions.close();
|
||||
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
nodeCreatorFeature.getters
|
||||
.searchBar()
|
||||
.find('input')
|
||||
.clear()
|
||||
.type('Customer Datastore (n8n training)');
|
||||
nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click();
|
||||
nodeCreatorFeature.getters.getCreatorItem('Get All People').click();
|
||||
NDVModal.actions.close();
|
||||
|
||||
WorkflowPage.actions.saveWorkflowOnButtonClick();
|
||||
WorkflowPage.actions.activateWorkflow();
|
||||
WorkflowPage.getters.activatorSwitch().should('have.class', 'is-checked');
|
||||
|
||||
// Wait for schedule 1s execution to mark user as having made a production execution
|
||||
cy.wait(1500);
|
||||
cy.reload();
|
||||
|
||||
// Action callout should not be visible after user has made a production execution
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
nodeCreatorFeature.getters
|
||||
.searchBar()
|
||||
.find('input')
|
||||
.clear()
|
||||
.type('Customer Datastore (n8n training)');
|
||||
nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click();
|
||||
|
||||
cy.getByTestId('actions-panel-activation-callout').should('not.exist');
|
||||
});
|
||||
|
||||
it('should show Trigger and Actions sections during search', () => {
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
|
||||
nodeCreatorFeature.getters
|
||||
.searchBar()
|
||||
.find('input')
|
||||
.clear()
|
||||
.type('Customer Datastore (n8n training)');
|
||||
nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click();
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('Non existent action name');
|
||||
|
||||
nodeCreatorFeature.getters.getCategoryItem('Triggers').should('be.visible');
|
||||
nodeCreatorFeature.getters.getCategoryItem('Actions').should('be.visible');
|
||||
cy.getByTestId('actions-panel-no-triggers-callout').should('be.visible');
|
||||
nodeCreatorFeature.getters.getCreatorItem('On a Schedule').should('be.visible');
|
||||
nodeCreatorFeature.getters.getCreatorItem('On a Webhook call').should('be.visible');
|
||||
});
|
||||
|
||||
describe('should correctly append manual trigger for regular actions', () => {
|
||||
// For these sources, manual node should be added
|
||||
const sourcesWithAppend = [
|
||||
{
|
||||
name: 'canvas add button',
|
||||
handler: () => nodeCreatorFeature.getters.canvasAddButton().click(),
|
||||
},
|
||||
{
|
||||
name: 'plus button',
|
||||
handler: () => nodeCreatorFeature.getters.plusButton().click(),
|
||||
},
|
||||
// We can't test this one because it's not possible to trigger tab key in Cypress
|
||||
// only way is to use `realPress` which is hanging the tests in Electron for some reason
|
||||
// {
|
||||
// name: 'tab key',
|
||||
// handler: () => cy.realPress('Tab'),
|
||||
// },
|
||||
];
|
||||
sourcesWithAppend.forEach((source) => {
|
||||
it(`should append manual trigger when source is ${source.name}`, () => {
|
||||
source.handler();
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n');
|
||||
nodeCreatorFeature.getters.getCreatorItem('n8n').click();
|
||||
nodeCreatorFeature.getters.getCategoryItem('Actions').click();
|
||||
nodeCreatorFeature.getters.getCreatorItem('Create a credential').click();
|
||||
NDVModal.actions.close();
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 2);
|
||||
});
|
||||
});
|
||||
|
||||
// @TODO FIX ADDING 2 NODES IN ONE GO
|
||||
it('should not append manual trigger when source is canvas related', () => {
|
||||
nodeCreatorFeature.getters.canvasAddButton().click();
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n');
|
||||
nodeCreatorFeature.getters.getCreatorItem('n8n').click();
|
||||
nodeCreatorFeature.getters.getCategoryItem('Actions').click();
|
||||
nodeCreatorFeature.getters.getCreatorItem('Create a credential').click();
|
||||
NDVModal.actions.close();
|
||||
WorkflowPage.actions.deleteNode('When clicking ‘Execute workflow’');
|
||||
WorkflowPage.getters.canvasNodePlusEndpointByName('Create a credential').click();
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n');
|
||||
nodeCreatorFeature.getters.getCreatorItem('n8n').click();
|
||||
nodeCreatorFeature.getters.getCategoryItem('Actions').click();
|
||||
nodeCreatorFeature.getters.getCreatorItem('Create a credential').click();
|
||||
NDVModal.actions.close();
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 2);
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
WorkflowPage.actions.addNodeBetweenNodes(
|
||||
'Create a credential',
|
||||
'Create a credential1',
|
||||
'Summarize',
|
||||
);
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 3);
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly append a No Op node when Loop Over Items node is added (from add button)', () => {
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').type('Loop Over Items');
|
||||
nodeCreatorFeature.getters.getCreatorItem('Loop Over Items').click();
|
||||
NDVModal.actions.close();
|
||||
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 3);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 3);
|
||||
|
||||
WorkflowPage.getters.getConnectionBetweenNodes('Loop Over Items', 'Replace Me').should('exist');
|
||||
WorkflowPage.getters.getConnectionBetweenNodes('Replace Me', 'Loop Over Items').should('exist');
|
||||
});
|
||||
|
||||
it('should correctly append a No Op node when Loop Over Items node is added (from connection)', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas('Manual');
|
||||
|
||||
cy.getByTestId('canvas-handle-plus').click();
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').type('Loop Over Items');
|
||||
nodeCreatorFeature.getters.getCreatorItem('Loop Over Items').click();
|
||||
NDVModal.actions.close();
|
||||
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 3);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 3);
|
||||
|
||||
WorkflowPage.getters.getConnectionBetweenNodes('Loop Over Items', 'Replace Me').should('exist');
|
||||
WorkflowPage.getters.getConnectionBetweenNodes('Replace Me', 'Loop Over Items').should('exist');
|
||||
});
|
||||
|
||||
// Skipping while we wait for a decision on how to handle the search results
|
||||
// eslint-disable-next-line n8n-local-rules/no-skipped-tests
|
||||
it.skip('should have most relevant nodes on top when searching', () => {
|
||||
nodeCreatorFeature.getters.canvasAddButton().click();
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('email');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Email Trigger (IMAP)');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('Set');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Edit Fields (Set)');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('i');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', IF_NODE_NAME);
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Switch');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('sw');
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('Edit F');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Edit Fields (Set)');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('i');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', IF_NODE_NAME);
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Switch');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('IF');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', IF_NODE_NAME);
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Switch');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('sw');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Switch');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('swit');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Switch');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('red');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Redis');
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Reddit');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('redd');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Reddit');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('wh');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Webhook');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('web');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Webflow');
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Webhook');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('webh');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Webhook');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('func');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Code');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('cod');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Coda');
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Code');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('code');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Code');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('js');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Code');
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Edit Fields (Set)');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('fi');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Filter');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('filt');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Filter');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('manu');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Manual Trigger');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('sse');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'SSE Trigger');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('cmpar');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Compare Datasets');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('fb');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Facebook Trigger');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('crn');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Schedule Trigger');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('cron');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Schedule Trigger');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('sch');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Schedule Trigger');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('time');
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Schedule Trigger');
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(2).should('have.text', 'Wait');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('mail');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Mailgun');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('mailc');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Mailcheck');
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Mailchimp');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('api');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'HTTP Request');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('s3');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'S3');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('no op');
|
||||
nodeCreatorFeature.getters
|
||||
.nodeItemName()
|
||||
.first()
|
||||
.should('have.text', 'No Operation, do nothing');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('do no');
|
||||
nodeCreatorFeature.getters
|
||||
.nodeItemName()
|
||||
.first()
|
||||
.should('have.text', 'No Operation, do nothing');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('htt');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'HTTP Request');
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Webhook');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('http');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'HTTP Request');
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Webhook');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('wa');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Wait');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('wait');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Wait');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('spreadsheet');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Convert to File');
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'Extract from File');
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(2).should('have.text', 'Google Sheets');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('sheets');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Google Sheets');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('ggle she');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Google Sheets');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('hub');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'HubSpot');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('git');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'Git');
|
||||
nodeCreatorFeature.getters.nodeItemName().eq(1).should('have.text', 'GitHub');
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('gith');
|
||||
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'GitHub');
|
||||
});
|
||||
|
||||
it('should show vector stores actions', () => {
|
||||
const actions = [
|
||||
'Get ranked documents from vector store',
|
||||
'Add documents to vector store',
|
||||
'Retrieve documents for Chain/Tool as Vector Store',
|
||||
];
|
||||
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
|
||||
nodeCreatorFeature.getters.searchBar().find('input').clear().type('Vector Store');
|
||||
|
||||
getNodeCreatorItems().then((items) => {
|
||||
const vectorStores = items.map((_i, el) => el.innerText);
|
||||
|
||||
// Loop over all vector stores and check if they have the three actions
|
||||
vectorStores.each((_i, vectorStore) => {
|
||||
if (vectorStore.includes('RAG starter template')) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodeCreatorFeature.getters.getCreatorItem(vectorStore).click();
|
||||
actions.forEach((action) => {
|
||||
nodeCreatorFeature.getters.getCreatorItem(action).should('be.visible').realHover();
|
||||
});
|
||||
cy.realPress('ArrowLeft');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should add node directly for sub-connection as vector store', () => {
|
||||
addNodeToCanvas('Question and Answer Chain', true);
|
||||
addRetrieverNodeToParent('Vector Store Retriever', 'Question and Answer Chain');
|
||||
cy.realPress('Escape');
|
||||
addVectorStoreNodeToParent('Simple Vector Store', 'Vector Store Retriever');
|
||||
cy.realPress('Escape');
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
});
|
||||
|
||||
it('should add node directly for sub-connection as tool', () => {
|
||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
|
||||
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
||||
clickGetBackToCanvas();
|
||||
|
||||
addVectorStoreToolToParent('Simple Vector Store', AGENT_NODE_NAME);
|
||||
});
|
||||
|
||||
it('should insert node to canvas with sendAndWait operation selected', () => {
|
||||
nodeCreatorFeature.getters.canvasAddButton().click();
|
||||
WorkflowPage.actions.addNodeToCanvas('Manual', false);
|
||||
nodeCreatorFeature.actions.openNodeCreator();
|
||||
cy.contains('Human in the loop').click();
|
||||
nodeCreatorFeature.getters.getCreatorItem('Slack').click();
|
||||
cy.contains('Send and Wait for Response').should('exist');
|
||||
});
|
||||
});
|
||||
255
packages/testing/playwright/AI-TEST-CHEAT-SHEET.md
Normal file
255
packages/testing/playwright/AI-TEST-CHEAT-SHEET.md
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
# 🚀 n8n Playwright Test Writing Cheat Sheet
|
||||
|
||||
> **For AI Assistants**: This guide provides quick reference patterns for writing n8n Playwright tests using the established architecture.
|
||||
|
||||
## Quick Start Navigation Methods
|
||||
|
||||
### **n8n.start.*** Methods (Test Entry Points)
|
||||
```typescript
|
||||
// Start from home page
|
||||
await n8n.start.fromHome();
|
||||
|
||||
// Start with blank canvas for new workflow
|
||||
await n8n.start.fromBlankCanvas();
|
||||
|
||||
// Start with new project + blank canvas (returns projectId)
|
||||
const projectId = await n8n.start.fromNewProjectBlankCanvas();
|
||||
|
||||
// Start with just a new project (no canvas)
|
||||
const projectId = await n8n.start.fromNewProject();
|
||||
|
||||
// Import and start from existing workflow JSON
|
||||
const result = await n8n.start.fromImportedWorkflow('simple-webhook-test.json');
|
||||
const { workflowId, webhookPath } = result;
|
||||
```
|
||||
|
||||
### **n8n.navigate.*** Methods (Page Navigation)
|
||||
```typescript
|
||||
// Basic navigation
|
||||
await n8n.navigate.toHome();
|
||||
await n8n.navigate.toWorkflow('new');
|
||||
await n8n.navigate.toWorkflows(projectId);
|
||||
|
||||
// Settings & admin
|
||||
await n8n.navigate.toVariables();
|
||||
await n8n.navigate.toCredentials(projectId);
|
||||
await n8n.navigate.toLogStreaming();
|
||||
await n8n.navigate.toCommunityNodes();
|
||||
|
||||
// Project-specific navigation
|
||||
await n8n.navigate.toProject(projectId);
|
||||
await n8n.navigate.toProjectSettings(projectId);
|
||||
```
|
||||
|
||||
## Common Test Patterns
|
||||
|
||||
### **Basic Workflow Test**
|
||||
```typescript
|
||||
test('should create and execute workflow', async ({ n8n }) => {
|
||||
await n8n.start.fromBlankCanvas();
|
||||
await n8n.canvas.addNode('Manual Trigger');
|
||||
await n8n.canvas.addNode('Set');
|
||||
await n8n.workflowComposer.executeWorkflowAndWaitForNotification('Success');
|
||||
});
|
||||
```
|
||||
|
||||
### **Imported Workflow Test**
|
||||
```typescript
|
||||
test('should import and test webhook', async ({ n8n }) => {
|
||||
const { webhookPath } = await n8n.start.fromImportedWorkflow('webhook-test.json');
|
||||
|
||||
await n8n.canvas.clickExecuteWorkflowButton();
|
||||
const response = await n8n.page.request.post(`/webhook-test/${webhookPath}`, {
|
||||
data: { message: 'Hello' }
|
||||
});
|
||||
expect(response.ok()).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
### **Project-Scoped Test**
|
||||
```typescript
|
||||
test('should create credential in project', async ({ n8n }) => {
|
||||
const projectId = await n8n.start.fromNewProject();
|
||||
await n8n.navigate.toCredentials(projectId);
|
||||
|
||||
await n8n.credentialsComposer.createFromList(
|
||||
'Notion API',
|
||||
{ apiKey: '12345' },
|
||||
{ name: `cred-${nanoid()}` }
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### **Node Configuration Test**
|
||||
```typescript
|
||||
test('should configure HTTP Request node', async ({ n8n }) => {
|
||||
await n8n.start.fromBlankCanvas();
|
||||
await n8n.canvas.addNode('Manual Trigger');
|
||||
await n8n.canvas.addNode('HTTP Request');
|
||||
|
||||
await n8n.ndv.fillParameterInput('URL', 'https://api.example.com');
|
||||
await n8n.ndv.close();
|
||||
await n8n.canvas.saveWorkflow();
|
||||
});
|
||||
```
|
||||
|
||||
## Test Setup Patterns
|
||||
|
||||
### **Feature Flags Setup**
|
||||
```typescript
|
||||
test.beforeEach(async ({ n8n, api }) => {
|
||||
await api.enableFeature('sharing');
|
||||
await api.enableFeature('folders');
|
||||
await api.enableFeature('projectRole:admin');
|
||||
await api.setMaxTeamProjectsQuota(-1);
|
||||
await n8n.goHome();
|
||||
});
|
||||
```
|
||||
|
||||
### **API + UI Combined Test**
|
||||
```typescript
|
||||
test('should use API-created credential in UI', async ({ n8n, api }) => {
|
||||
const projectId = await n8n.start.fromNewProjectBlankCanvas();
|
||||
|
||||
// Create via API
|
||||
await api.credentialApi.createCredential({
|
||||
name: 'test-cred',
|
||||
type: 'notionApi',
|
||||
data: { apiKey: '12345' },
|
||||
projectId
|
||||
});
|
||||
|
||||
// Verify in UI
|
||||
await n8n.canvas.addNode('Notion');
|
||||
await expect(n8n.ndv.getCredentialSelect()).toHaveValue('test-cred');
|
||||
});
|
||||
```
|
||||
|
||||
### **Error/Edge Case Testing**
|
||||
```typescript
|
||||
test('should handle workflow execution error', async ({ n8n }) => {
|
||||
await n8n.start.fromImportedWorkflow('failing-workflow.json');
|
||||
await n8n.workflowComposer.executeWorkflowAndWaitForNotification('Problem in node');
|
||||
await expect(n8n.canvas.getErrorIcon()).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
## Architecture Guidelines
|
||||
|
||||
### **Four-Layer UI Testing Architecture**
|
||||
```
|
||||
Tests (*.spec.ts)
|
||||
↓ uses
|
||||
Composables (*Composer.ts) - Business workflows
|
||||
↓ orchestrates
|
||||
Page Objects (*Page.ts) - UI interactions
|
||||
↓ extends
|
||||
BasePage - Common utilities
|
||||
```
|
||||
|
||||
### **When to Use Each Layer**
|
||||
- **Tests**: High-level scenarios, readable business logic
|
||||
- **Composables**: Multi-step workflows (e.g., `executeWorkflowAndWaitForNotification`)
|
||||
- **Page Objects**: Simple UI actions (e.g., `clickSaveButton`, `fillInput`)
|
||||
- **BasePage**: Generic interactions (e.g., `clickByTestId`, `fillByTestId`)
|
||||
|
||||
### **Method Naming Conventions**
|
||||
```typescript
|
||||
// Page Object Getters (No async, return Locator)
|
||||
getSearchBar() { return this.page.getByTestId('search'); }
|
||||
|
||||
// Page Object Actions (async, return void)
|
||||
async clickSaveButton() { await this.clickButtonByName('Save'); }
|
||||
|
||||
// Page Object Queries (async, return data)
|
||||
async getNotificationCount(): Promise<number> { /* ... */ }
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### **Most Common Entry Points**
|
||||
- `n8n.start.fromBlankCanvas()` - New workflow from scratch
|
||||
- `n8n.start.fromImportedWorkflow('file.json')` - Test existing workflow
|
||||
- `n8n.start.fromNewProjectBlankCanvas()` - Project-scoped testing
|
||||
|
||||
### **Most Common Navigation**
|
||||
- `n8n.navigate.toCredentials(projectId)` - Credential management
|
||||
- `n8n.navigate.toVariables()` - Environment variables
|
||||
- `n8n.navigate.toWorkflow('new')` - New workflow canvas
|
||||
|
||||
### **Essential Assertions**
|
||||
```typescript
|
||||
// UI state verification
|
||||
await expect(n8n.canvas.canvasPane()).toBeVisible();
|
||||
await expect(n8n.notifications.getNotificationByTitle('Success')).toBeVisible();
|
||||
await expect(n8n.ndv.getCredentialSelect()).toHaveValue(name);
|
||||
|
||||
// Node and workflow verification
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||
await expect(n8n.canvas.nodeByName('HTTP Request')).toBeVisible();
|
||||
```
|
||||
|
||||
### **Common Composable Methods**
|
||||
```typescript
|
||||
// Workflow operations
|
||||
await n8n.workflowComposer.executeWorkflowAndWaitForNotification('Success');
|
||||
await n8n.workflowComposer.createWorkflow('My Workflow');
|
||||
|
||||
// Project operations
|
||||
const { projectName, projectId } = await n8n.projectComposer.createProject();
|
||||
|
||||
// Credential operations
|
||||
await n8n.credentialsComposer.createFromList('Notion API', { apiKey: '123' });
|
||||
await n8n.credentialsComposer.createFromNdv({ apiKey: '123' });
|
||||
```
|
||||
|
||||
### **Dynamic Data Patterns**
|
||||
```typescript
|
||||
// Use nanoid for unique identifiers
|
||||
import { nanoid } from 'nanoid';
|
||||
const workflowName = `Test Workflow ${nanoid()}`;
|
||||
const credentialName = `cred-${nanoid()}`;
|
||||
|
||||
// Use timestamps for uniqueness
|
||||
const projectName = `Project ${Date.now()}`;
|
||||
```
|
||||
|
||||
## AI Guidelines
|
||||
|
||||
### **✅ DO**
|
||||
- Always use `n8n.start.*` methods for test entry points
|
||||
- Use composables for business workflows, not page objects directly in tests
|
||||
- Use `nanoid()` or timestamps for unique test data
|
||||
- Follow the 4-layer architecture pattern
|
||||
- Use proper waiting with `expect().toBeVisible()` instead of `waitForTimeout`
|
||||
|
||||
### **❌ DON'T**
|
||||
- Use raw `page.goto()` instead of navigation helpers
|
||||
- Mix business logic in page objects (move to composables)
|
||||
- Use hardcoded selectors in tests (use page object getters)
|
||||
- Create overly specific methods (keep them reusable)
|
||||
- Use `any` types or `waitForTimeout`
|
||||
|
||||
### **Test Structure Template**
|
||||
```typescript
|
||||
import { test, expect } from '../../fixtures/base';
|
||||
|
||||
test.describe('Feature Name', () => {
|
||||
test.beforeEach(async ({ n8n, api }) => {
|
||||
// Feature flags and setup
|
||||
await api.enableFeature('requiredFeature');
|
||||
await n8n.goHome();
|
||||
});
|
||||
|
||||
test('should perform specific action', async ({ n8n }) => {
|
||||
// 1. Setup/Navigation
|
||||
await n8n.start.fromBlankCanvas();
|
||||
|
||||
// 2. Actions using composables
|
||||
await n8n.workflowComposer.createBasicWorkflow();
|
||||
|
||||
// 3. Assertions
|
||||
await expect(n8n.notifications.getNotificationByTitle('Success')).toBeVisible();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
|
@ -7,6 +7,7 @@ import { resolveFromRoot } from '../utils/path-helper';
|
|||
import { CredentialModal } from './components/CredentialModal';
|
||||
import { FocusPanel } from './components/FocusPanel';
|
||||
import { LogsPanel } from './components/LogsPanel';
|
||||
import { NodeCreator } from './components/NodeCreator';
|
||||
import { StickyComponent } from './components/StickyComponent';
|
||||
|
||||
export class CanvasPage extends BasePage {
|
||||
|
|
@ -14,6 +15,7 @@ export class CanvasPage extends BasePage {
|
|||
readonly logsPanel = new LogsPanel(this.page.getByTestId('logs-panel'));
|
||||
readonly focusPanel = new FocusPanel(this.page.getByTestId('focus-panel'));
|
||||
readonly credentialModal = new CredentialModal(this.page.getByTestId('editCredential-modal'));
|
||||
readonly nodeCreator = new NodeCreator(this.page);
|
||||
|
||||
saveWorkflowButton(): Locator {
|
||||
return this.page.getByRole('button', { name: 'Save' });
|
||||
|
|
@ -387,6 +389,10 @@ export class CanvasPage extends BasePage {
|
|||
return this.page.locator('[data-test-id="edge"]');
|
||||
}
|
||||
|
||||
getConnectionBetweenNodes(sourceNodeName: string, targetNodeName: string): Locator {
|
||||
return this.connectionBetweenNodes(sourceNodeName, targetNodeName);
|
||||
}
|
||||
|
||||
canvasNodePlusEndpointByName(nodeName: string): Locator {
|
||||
return this.page
|
||||
.locator(
|
||||
|
|
@ -736,4 +742,8 @@ export class CanvasPage extends BasePage {
|
|||
getNodeWarningStatusIndicator(nodeName: string): Locator {
|
||||
return this.nodeByName(nodeName).getByTestId('canvas-node-status-warning');
|
||||
}
|
||||
|
||||
getCanvasPlusButton(): Locator {
|
||||
return this.page.getByTestId('canvas-plus-button');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
114
packages/testing/playwright/pages/components/NodeCreator.ts
Normal file
114
packages/testing/playwright/pages/components/NodeCreator.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import type { Locator, Page } from '@playwright/test';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Node Creator component for adding nodes to workflows.
|
||||
* Used within CanvasPage as `n8n.canvas.nodeCreator.*`
|
||||
*
|
||||
* @example
|
||||
* // Access via canvas page
|
||||
* await n8n.canvas.nodeCreator.open();
|
||||
* await n8n.canvas.nodeCreator.searchFor('Gmail');
|
||||
* await n8n.canvas.nodeCreator.selectItem('Gmail');
|
||||
*/
|
||||
export class NodeCreator {
|
||||
constructor(private page: Page) {}
|
||||
|
||||
// Core locators
|
||||
getRoot(): Locator {
|
||||
return this.page.getByTestId('node-creator');
|
||||
}
|
||||
|
||||
getSearchBar(): Locator {
|
||||
return this.page.getByTestId('node-creator-search-bar');
|
||||
}
|
||||
|
||||
getNodeItems(): Locator {
|
||||
return this.page.getByTestId('item-iterator-item');
|
||||
}
|
||||
|
||||
getActionItems(): Locator {
|
||||
return this.page.getByTestId('node-creator-action-item');
|
||||
}
|
||||
|
||||
getCategoryItems(): Locator {
|
||||
return this.page.getByTestId('node-creator-category-item');
|
||||
}
|
||||
|
||||
getTabs(): Locator {
|
||||
return this.page.getByTestId('node-creator-type-selector');
|
||||
}
|
||||
|
||||
getSelectedTab(): Locator {
|
||||
return this.getTabs().locator('.is-active');
|
||||
}
|
||||
|
||||
getActiveSubcategory(): Locator {
|
||||
return this.page.getByTestId('nodes-list-header').first();
|
||||
}
|
||||
|
||||
getNoResults(): Locator {
|
||||
return this.page.getByTestId('node-creator-no-results');
|
||||
}
|
||||
|
||||
getTriggerText(): Locator {
|
||||
return this.page.getByText('What triggers this workflow?');
|
||||
}
|
||||
|
||||
getNextText(): Locator {
|
||||
return this.page.getByText('What happens next?');
|
||||
}
|
||||
|
||||
// Item getters
|
||||
getItem(text: string): Locator {
|
||||
return this.getNodeItems().filter({ hasText: text }).first();
|
||||
}
|
||||
|
||||
getCategoryItem(text: string): Locator {
|
||||
return this.getCategoryItems().filter({ hasText: text });
|
||||
}
|
||||
|
||||
getPanelIcon(nodeName: string): Locator {
|
||||
return this.getItem(nodeName).locator('[class*="panelIcon"]');
|
||||
}
|
||||
|
||||
// Actions
|
||||
async open(): Promise<void> {
|
||||
await this.page.getByTestId('canvas-plus-button').click();
|
||||
await expect(this.getRoot()).toBeVisible();
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
await this.page.keyboard.press('Escape');
|
||||
}
|
||||
|
||||
async searchFor(text: string): Promise<void> {
|
||||
await this.getSearchBar().fill(text);
|
||||
}
|
||||
|
||||
async clearSearch(): Promise<void> {
|
||||
await this.getSearchBar().clear();
|
||||
}
|
||||
|
||||
async selectItem(text: string): Promise<void> {
|
||||
await this.getItem(text).click();
|
||||
}
|
||||
|
||||
async selectCategoryItem(text: string): Promise<void> {
|
||||
await this.getCategoryItem(text).click();
|
||||
}
|
||||
|
||||
async navigateToSubcategory(category: string): Promise<void> {
|
||||
await this.getItem(category).click();
|
||||
await expect(this.getActiveSubcategory()).toContainText(category);
|
||||
}
|
||||
|
||||
async goBackFromSubcategory(): Promise<void> {
|
||||
await this.getActiveSubcategory().locator('button').click();
|
||||
}
|
||||
|
||||
async selectWithKeyboard(direction: 'up' | 'down' | 'right'): Promise<void> {
|
||||
const key = direction === 'up' ? 'ArrowUp' : direction === 'down' ? 'ArrowDown' : 'ArrowRight';
|
||||
await this.page.keyboard.press(key);
|
||||
}
|
||||
}
|
||||
|
|
@ -182,7 +182,7 @@ test.describe('Projects', () => {
|
|||
// Verify owner cannot change their own role
|
||||
const ownerRow = memberRows.first();
|
||||
const roleDropdown = ownerRow.getByTestId('project-member-role-dropdown');
|
||||
await expect(roleDropdown).not.toBeVisible();
|
||||
await expect(roleDropdown).toBeHidden();
|
||||
});
|
||||
|
||||
test('should display role dropdown for members but not for current user @auth:owner', async ({
|
||||
|
|
@ -197,7 +197,7 @@ test.describe('Projects', () => {
|
|||
|
||||
// Current user (owner) should not have a role dropdown
|
||||
const currentUserRow = n8n.page.locator('tbody tr').first();
|
||||
await expect(currentUserRow.getByTestId('project-member-role-dropdown')).not.toBeVisible();
|
||||
await expect(currentUserRow.getByTestId('project-member-role-dropdown')).toBeHidden();
|
||||
|
||||
// The role should be displayed as static text for the current user
|
||||
await expect(currentUserRow.getByText('Admin')).toBeVisible();
|
||||
|
|
@ -246,7 +246,7 @@ test.describe('Projects', () => {
|
|||
await n8n.projectSettings.fillProjectName('Valid Project Name');
|
||||
|
||||
// Save button should now be enabled
|
||||
await expect(saveButton).not.toBeDisabled();
|
||||
await expect(saveButton).toBeEnabled();
|
||||
});
|
||||
|
||||
test('should handle unsaved changes state @auth:owner', async ({ n8n }) => {
|
||||
|
|
@ -265,8 +265,8 @@ test.describe('Projects', () => {
|
|||
await n8n.projectSettings.fillProjectName('Modified Name');
|
||||
|
||||
// Save and cancel buttons should now be enabled
|
||||
await expect(n8n.page.getByTestId('project-settings-save-button')).not.toBeDisabled();
|
||||
await expect(n8n.page.getByTestId('project-settings-cancel-button')).not.toBeDisabled();
|
||||
await expect(n8n.page.getByTestId('project-settings-save-button')).toBeEnabled();
|
||||
await expect(n8n.page.getByTestId('project-settings-cancel-button')).toBeEnabled();
|
||||
|
||||
// Unsaved changes message should be visible
|
||||
await expect(n8n.page.getByText('You have unsaved changes')).toBeVisible();
|
||||
|
|
|
|||
|
|
@ -26,14 +26,14 @@ test.describe('Focus panel', () => {
|
|||
// Assert that mapper is closed but the Set node is still selected and shown in
|
||||
await n8n.canvas.canvasBody().click({ position: { x: 0, y: 0 } });
|
||||
|
||||
await expect(n8n.canvas.focusPanel.getMapper()).not.toBeVisible();
|
||||
await expect(n8n.canvas.focusPanel.getMapper()).toBeHidden();
|
||||
await expect(n8n.canvas.focusPanel.getHeader()).toHaveText('Set');
|
||||
await expect(n8n.canvas.selectedNodes()).toHaveCount(1);
|
||||
|
||||
// Assert that another click on canvas does de-select the Set node
|
||||
await n8n.canvas.canvasBody().click({ position: { x: 0, y: 0 } });
|
||||
|
||||
await expect(n8n.canvas.focusPanel.getHeader()).not.toBeVisible();
|
||||
await expect(n8n.canvas.focusPanel.getHeader()).toBeHidden();
|
||||
await expect(n8n.canvas.selectedNodes()).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -86,12 +86,10 @@ test.describe('Canvas Node Actions', () => {
|
|||
|
||||
test('should filter nodes by search term', async ({ n8n }) => {
|
||||
await n8n.canvas.clickCanvasPlusButton();
|
||||
const initialCount = await n8n.canvas.nodeCreatorNodeItems().count();
|
||||
await n8n.canvas.fillNodeCreatorSearchBar('HTTP');
|
||||
const filteredCount = await n8n.canvas.nodeCreatorNodeItems().count();
|
||||
|
||||
expect(filteredCount).toBeLessThan(initialCount);
|
||||
expect(filteredCount).toBeGreaterThan(0);
|
||||
const filteredItems = n8n.canvas.nodeCreatorNodeItems();
|
||||
await expect(filteredItems.first()).toContainText('HTTP');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
import { test, expect } from '../../../fixtures/base';
|
||||
|
||||
test.describe('Node Creator Actions', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.start.fromBlankCanvas();
|
||||
});
|
||||
|
||||
test('should add node to canvas from actions panel', async ({ n8n }) => {
|
||||
const editImageNode = 'Edit Image';
|
||||
|
||||
await n8n.canvas.nodeCreator.open();
|
||||
await n8n.canvas.nodeCreator.searchFor(editImageNode);
|
||||
await n8n.canvas.nodeCreator.selectItem(editImageNode);
|
||||
|
||||
await expect(n8n.canvas.nodeCreator.getActiveSubcategory()).toContainText(editImageNode);
|
||||
await n8n.canvas.nodeCreator.selectItem('Crop Image');
|
||||
await expect(n8n.ndv.getContainer()).toBeVisible();
|
||||
await n8n.page.keyboard.press('Escape');
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||
});
|
||||
|
||||
test('should search through actions and confirm added action', async ({ n8n }) => {
|
||||
await n8n.canvas.nodeCreator.open();
|
||||
await n8n.canvas.nodeCreator.searchFor('ftp');
|
||||
await n8n.canvas.nodeCreator.selectItem('FTP');
|
||||
|
||||
await expect(n8n.canvas.nodeCreator.getActiveSubcategory()).toContainText('FTP');
|
||||
await n8n.canvas.nodeCreator.clearSearch();
|
||||
await n8n.canvas.nodeCreator.searchFor('rename');
|
||||
await n8n.canvas.nodeCreator.selectItem('Rename');
|
||||
|
||||
await expect(n8n.ndv.getContainer()).toBeVisible();
|
||||
await n8n.page.keyboard.press('Escape');
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||
});
|
||||
|
||||
test('should show multiple actions for multi-action nodes', async ({ n8n }) => {
|
||||
await n8n.canvas.nodeCreator.open();
|
||||
await n8n.canvas.nodeCreator.searchFor('OpenWeatherMap');
|
||||
await n8n.canvas.nodeCreator.selectItem('OpenWeatherMap');
|
||||
|
||||
await expect(n8n.canvas.nodeCreator.getActiveSubcategory()).toContainText('OpenWeatherMap');
|
||||
await expect(n8n.canvas.nodeCreator.getNodeItems().first()).toBeVisible();
|
||||
await expect(n8n.canvas.nodeCreator.getNodeItems().nth(1)).toBeVisible();
|
||||
|
||||
await n8n.canvas.nodeCreator.getNodeItems().first().click();
|
||||
await n8n.page.keyboard.press('Escape');
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||
});
|
||||
|
||||
test('should add node with specific operation configuration', async ({ n8n }) => {
|
||||
await n8n.canvas.nodeCreator.open();
|
||||
await n8n.canvas.nodeCreator.searchFor('Slack');
|
||||
await n8n.canvas.nodeCreator.selectItem('Slack');
|
||||
|
||||
await expect(n8n.canvas.nodeCreator.getActiveSubcategory()).toContainText('Slack');
|
||||
await n8n.canvas.nodeCreator.getNodeItems().first().click();
|
||||
await n8n.page.keyboard.press('Escape');
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
import { MANUAL_TRIGGER_NODE_DISPLAY_NAME } from '../../../config/constants';
|
||||
import { test, expect } from '../../../fixtures/base';
|
||||
|
||||
test.describe('Node Creator Categories', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.start.fromBlankCanvas();
|
||||
});
|
||||
|
||||
test('should have "Actions" section collapsed when opening actions view from Trigger root view', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
await n8n.canvas.nodeCreator.open();
|
||||
await n8n.canvas.nodeCreator.searchFor('ActiveCampaign');
|
||||
await n8n.canvas.nodeCreator.selectItem('ActiveCampaign');
|
||||
|
||||
await expect(n8n.canvas.nodeCreator.getCategoryItem('Actions')).toBeVisible();
|
||||
await expect(n8n.canvas.nodeCreator.getCategoryItem('Triggers')).toBeVisible();
|
||||
|
||||
await expect(n8n.canvas.nodeCreator.getCategoryItem('Triggers').locator('..')).toHaveAttribute(
|
||||
'data-category-collapsed',
|
||||
'false',
|
||||
);
|
||||
|
||||
await expect(n8n.canvas.nodeCreator.getCategoryItem('Actions').locator('..')).toHaveAttribute(
|
||||
'data-category-collapsed',
|
||||
'true',
|
||||
);
|
||||
|
||||
await n8n.canvas.nodeCreator.selectCategoryItem('Actions');
|
||||
await expect(n8n.canvas.nodeCreator.getCategoryItem('Actions').locator('..')).toHaveAttribute(
|
||||
'data-category-collapsed',
|
||||
'false',
|
||||
);
|
||||
});
|
||||
|
||||
test('should have "Triggers" section collapsed when opening actions view from Regular root view', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
await n8n.canvas.addNode('Manual Trigger');
|
||||
|
||||
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
await n8n.canvas.nodeCreator.searchFor('n8n');
|
||||
await n8n.canvas.nodeCreator.getNodeItems().filter({ hasText: 'n8n' }).first().click();
|
||||
|
||||
await expect(n8n.canvas.nodeCreator.getCategoryItem('Actions').locator('..')).toHaveAttribute(
|
||||
'data-category-collapsed',
|
||||
'false',
|
||||
);
|
||||
|
||||
await n8n.canvas.nodeCreator.selectCategoryItem('Actions');
|
||||
await expect(n8n.canvas.nodeCreator.getCategoryItem('Actions').locator('..')).toHaveAttribute(
|
||||
'data-category-collapsed',
|
||||
'true',
|
||||
);
|
||||
|
||||
await expect(n8n.canvas.nodeCreator.getCategoryItem('Triggers').locator('..')).toHaveAttribute(
|
||||
'data-category-collapsed',
|
||||
'true',
|
||||
);
|
||||
|
||||
await n8n.canvas.nodeCreator.selectCategoryItem('Triggers');
|
||||
await expect(n8n.canvas.nodeCreator.getCategoryItem('Triggers').locator('..')).toHaveAttribute(
|
||||
'data-category-collapsed',
|
||||
'false',
|
||||
);
|
||||
});
|
||||
|
||||
test('should show callout and two suggested nodes if node has no trigger actions', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
await n8n.canvas.nodeCreator.open();
|
||||
await n8n.canvas.nodeCreator.searchFor('Customer Datastore (n8n training)');
|
||||
await n8n.canvas.nodeCreator.selectItem('Customer Datastore (n8n training)');
|
||||
|
||||
await expect(n8n.page.getByTestId('actions-panel-no-triggers-callout')).toBeVisible();
|
||||
await expect(n8n.canvas.nodeCreator.getItem('On a Schedule')).toBeVisible();
|
||||
await expect(n8n.canvas.nodeCreator.getItem('On a Webhook call')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show intro callout if user has not made a production execution', async ({ n8n }) => {
|
||||
await n8n.canvas.nodeCreator.open();
|
||||
await n8n.canvas.nodeCreator.searchFor('Customer Datastore (n8n training)');
|
||||
await n8n.canvas.nodeCreator.selectItem('Customer Datastore (n8n training)');
|
||||
|
||||
await expect(n8n.page.getByTestId('actions-panel-activation-callout')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show Trigger and Actions sections during search', async ({ n8n }) => {
|
||||
await n8n.canvas.nodeCreator.open();
|
||||
await n8n.canvas.nodeCreator.searchFor('Customer Datastore (n8n training)');
|
||||
await n8n.canvas.nodeCreator.selectItem('Customer Datastore (n8n training)');
|
||||
|
||||
await n8n.canvas.nodeCreator.searchFor('Non existent action name');
|
||||
|
||||
await expect(n8n.canvas.nodeCreator.getCategoryItem('Triggers')).toBeVisible();
|
||||
await expect(n8n.canvas.nodeCreator.getCategoryItem('Actions')).toBeVisible();
|
||||
await expect(n8n.page.getByTestId('actions-panel-no-triggers-callout')).toBeVisible();
|
||||
await expect(n8n.canvas.nodeCreator.getItem('On a Schedule')).toBeVisible();
|
||||
await expect(n8n.canvas.nodeCreator.getItem('On a Webhook call')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import { MANUAL_TRIGGER_NODE_DISPLAY_NAME } from '../../../config/constants';
|
||||
import { test, expect } from '../../../fixtures/base';
|
||||
|
||||
test.describe('Node Creator Navigation', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.start.fromBlankCanvas();
|
||||
});
|
||||
|
||||
test('should open node creator on trigger tab if no trigger is on canvas', async ({ n8n }) => {
|
||||
await n8n.canvas.clickCanvasPlusButton();
|
||||
await expect(n8n.canvas.nodeCreator.getRoot()).toBeVisible();
|
||||
await expect(n8n.canvas.nodeCreator.getTriggerText()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should navigate subcategory and return', async ({ n8n }) => {
|
||||
await n8n.canvas.nodeCreator.open();
|
||||
|
||||
await n8n.canvas.nodeCreator.navigateToSubcategory('On app event');
|
||||
await expect(n8n.canvas.nodeCreator.getActiveSubcategory()).toContainText('On app event');
|
||||
|
||||
await n8n.canvas.nodeCreator.goBackFromSubcategory();
|
||||
await expect(n8n.canvas.nodeCreator.getActiveSubcategory()).not.toContainText('On app event');
|
||||
});
|
||||
|
||||
test('should search for nodes with various queries', async ({ n8n }) => {
|
||||
await n8n.canvas.nodeCreator.open();
|
||||
|
||||
await n8n.canvas.nodeCreator.searchFor('manual');
|
||||
await expect(n8n.canvas.nodeCreator.getNodeItems()).toHaveCount(1);
|
||||
|
||||
await n8n.canvas.nodeCreator.clearSearch();
|
||||
await n8n.canvas.nodeCreator.searchFor('manual123');
|
||||
await expect(n8n.canvas.nodeCreator.getNodeItems()).toHaveCount(0);
|
||||
await expect(n8n.canvas.nodeCreator.getNoResults()).toBeVisible();
|
||||
await expect(n8n.canvas.nodeCreator.getNoResults()).toContainText("We didn't make that... yet");
|
||||
|
||||
await n8n.canvas.nodeCreator.clearSearch();
|
||||
await n8n.canvas.nodeCreator.searchFor('edit image');
|
||||
await expect(n8n.canvas.nodeCreator.getNodeItems()).toHaveCount(1);
|
||||
|
||||
await n8n.canvas.nodeCreator.clearSearch();
|
||||
await n8n.canvas.nodeCreator.searchFor('this node totally does not exist');
|
||||
await expect(n8n.canvas.nodeCreator.getNodeItems()).toHaveCount(0);
|
||||
|
||||
await n8n.canvas.nodeCreator.clearSearch();
|
||||
await n8n.canvas.nodeCreator.navigateToSubcategory('On app event');
|
||||
|
||||
await n8n.canvas.nodeCreator.searchFor('edit image');
|
||||
await expect(
|
||||
n8n.canvas.nodeCreator.getCategoryItem('Results in other categories'),
|
||||
).toBeVisible();
|
||||
await expect(n8n.canvas.nodeCreator.getNodeItems()).toHaveCount(1);
|
||||
await expect(n8n.canvas.nodeCreator.getItem('Edit Image')).toBeVisible();
|
||||
|
||||
await n8n.canvas.nodeCreator.clearSearch();
|
||||
await n8n.canvas.nodeCreator.searchFor('edit image123123');
|
||||
await expect(n8n.canvas.nodeCreator.getNodeItems()).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('should check correct view panels after adding manual trigger', async ({ n8n }) => {
|
||||
await n8n.canvas.clickCanvasPlusButton();
|
||||
await expect(n8n.canvas.nodeCreator.getTriggerText()).toBeVisible();
|
||||
await n8n.canvas.nodeCreator.close();
|
||||
|
||||
await n8n.canvas.addNode('Manual Trigger');
|
||||
await expect(n8n.canvas.getCanvasPlusButton()).toBeHidden();
|
||||
|
||||
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
await expect(n8n.canvas.nodeCreator.getNextText()).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import { MANUAL_TRIGGER_NODE_DISPLAY_NAME } from '../../../config/constants';
|
||||
import { test, expect } from '../../../fixtures/base';
|
||||
|
||||
test.describe('Node Creator Special Nodes', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.start.fromBlankCanvas();
|
||||
});
|
||||
|
||||
test('should correctly append a No Op node when Loop Over Items node is added (from add button)', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
await n8n.canvas.nodeCreator.open();
|
||||
await n8n.canvas.nodeCreator.searchFor('Loop Over Items');
|
||||
await n8n.canvas.nodeCreator.selectItem('Loop Over Items');
|
||||
await n8n.ndv.close();
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3);
|
||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(3);
|
||||
await expect(n8n.canvas.nodeByName('Loop Over Items')).toBeVisible();
|
||||
await expect(n8n.canvas.nodeByName('Replace Me')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should correctly append a No Op node when Loop Over Items node is added (from connection)', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
await n8n.canvas.addNode('Manual Trigger');
|
||||
|
||||
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
await n8n.canvas.nodeCreator.searchFor('Loop Over Items');
|
||||
await n8n.canvas.nodeCreator.selectItem('Loop Over Items');
|
||||
await n8n.ndv.close();
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3);
|
||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(3);
|
||||
await expect(n8n.canvas.nodeByName('Loop Over Items')).toBeVisible();
|
||||
await expect(n8n.canvas.nodeByName('Replace Me')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should add a Send and Wait for Response node', async ({ n8n }) => {
|
||||
await n8n.canvas.addNode('Manual Trigger');
|
||||
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
|
||||
await n8n.canvas.nodeCreator.selectItem('Human in the loop');
|
||||
|
||||
await n8n.canvas.nodeCreator.selectItem('Slack');
|
||||
await n8n.ndv.setupHelper.setParameter('operation', 'Send and Wait for Response');
|
||||
await n8n.ndv.close();
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { MANUAL_TRIGGER_NODE_DISPLAY_NAME } from '../../../config/constants';
|
||||
import { test, expect } from '../../../fixtures/base';
|
||||
|
||||
test.describe('Node Creator Vector Stores', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.start.fromBlankCanvas();
|
||||
await n8n.canvas.addNode('Manual Trigger');
|
||||
});
|
||||
|
||||
test('should show vector stores actions', async ({ n8n }) => {
|
||||
const expectedActions = [
|
||||
'Get ranked documents from vector store',
|
||||
'Add documents to vector store',
|
||||
'Retrieve documents for Chain/Tool as Vector Store',
|
||||
'Retrieve documents for AI Agent as Tool',
|
||||
];
|
||||
|
||||
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
await n8n.canvas.nodeCreator.searchFor('Vector Store');
|
||||
|
||||
await expect(n8n.canvas.nodeCreator.getNodeItems().first()).toBeVisible();
|
||||
|
||||
await n8n.canvas.nodeCreator.getItem('Simple Vector Store').click();
|
||||
|
||||
for (const action of expectedActions) {
|
||||
await expect(n8n.canvas.nodeCreator.getItem(action)).toBeVisible();
|
||||
}
|
||||
|
||||
await n8n.canvas.nodeCreator.goBackFromSubcategory();
|
||||
await expect(n8n.canvas.nodeCreator.getNodeItems().first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should find vector store nodes in creator', async ({ n8n }) => {
|
||||
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
await n8n.canvas.nodeCreator.searchFor('Vector Store');
|
||||
|
||||
await expect(n8n.canvas.nodeCreator.getNodeItems().first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should search for specific vector store nodes', async ({ n8n }) => {
|
||||
await n8n.canvas.clickNodePlusEndpoint(MANUAL_TRIGGER_NODE_DISPLAY_NAME);
|
||||
await n8n.canvas.nodeCreator.searchFor('Simple Vector Store');
|
||||
|
||||
await expect(n8n.canvas.nodeCreator.getItem('Simple Vector Store')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { test, expect } from '../../../fixtures/base';
|
||||
|
||||
test.describe('Node Creator Workflow Building', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.start.fromBlankCanvas();
|
||||
});
|
||||
|
||||
test('should append manual trigger when adding action node from canvas add button', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
await n8n.canvas.clickCanvasPlusButton();
|
||||
await n8n.canvas.nodeCreator.searchFor('n8n');
|
||||
await n8n.canvas.nodeCreator.selectItem('n8n');
|
||||
await n8n.canvas.nodeCreator.selectCategoryItem('Actions');
|
||||
await n8n.canvas.nodeCreator.selectItem('Create a credential');
|
||||
await n8n.page.keyboard.press('Escape');
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||
await expect(n8n.canvas.nodeConnections()).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('should append manual trigger when adding action node from plus button', async ({ n8n }) => {
|
||||
await n8n.canvas.clickCanvasPlusButton();
|
||||
await n8n.canvas.nodeCreator.searchFor('n8n');
|
||||
await n8n.canvas.nodeCreator.selectItem('n8n');
|
||||
await n8n.canvas.nodeCreator.selectCategoryItem('Actions');
|
||||
await n8n.canvas.nodeCreator.selectItem('Create a credential');
|
||||
await n8n.page.keyboard.press('Escape');
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user