mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
test: Finish Sub-Workflow Inputs Testing (no-changelog) (#12460)
This commit is contained in:
parent
88c0838dd7
commit
a9077bb6f1
|
@ -2,7 +2,7 @@
|
|||
* Getters
|
||||
*/
|
||||
|
||||
import { getVisibleSelect } from '../utils/popper';
|
||||
import { getVisiblePopper, getVisibleSelect } from '../utils/popper';
|
||||
|
||||
export function getCredentialSelect(eq = 0) {
|
||||
return cy.getByTestId('node-credentials-select').eq(eq);
|
||||
|
@ -36,6 +36,18 @@ export function getOutputPanel() {
|
|||
return cy.getByTestId('output-panel');
|
||||
}
|
||||
|
||||
export function getFixedCollection(collectionName: string) {
|
||||
return cy.getByTestId(`fixed-collection-${collectionName}`);
|
||||
}
|
||||
|
||||
export function getResourceLocator(paramName: string) {
|
||||
return cy.getByTestId(`resource-locator-${paramName}`);
|
||||
}
|
||||
|
||||
export function getResourceLocatorInput(paramName: string) {
|
||||
return getResourceLocator(paramName).find('[data-test-id="rlc-input-container"]');
|
||||
}
|
||||
|
||||
export function getOutputPanelDataContainer() {
|
||||
return getOutputPanel().getByTestId('ndv-data-container');
|
||||
}
|
||||
|
@ -84,6 +96,30 @@ export function getOutputPanelRelatedExecutionLink() {
|
|||
return getOutputPanel().getByTestId('related-execution-link');
|
||||
}
|
||||
|
||||
export function getNodeOutputHint() {
|
||||
return cy.getByTestId('ndv-output-run-node-hint');
|
||||
}
|
||||
|
||||
export function getWorkflowCards() {
|
||||
return cy.getByTestId('resources-list-item');
|
||||
}
|
||||
|
||||
export function getWorkflowCard(workflowName: string) {
|
||||
return getWorkflowCards().contains(workflowName).parents('[data-test-id="resources-list-item"]');
|
||||
}
|
||||
|
||||
export function getWorkflowCardContent(workflowName: string) {
|
||||
return getWorkflowCard(workflowName).findChildByTestId('card-content');
|
||||
}
|
||||
|
||||
export function getNodeRunInfoStale() {
|
||||
return cy.getByTestId('node-run-info-stale');
|
||||
}
|
||||
|
||||
export function getNodeOutputErrorMessage() {
|
||||
return getOutputPanel().findChildByTestId('node-error-message');
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
|
@ -110,12 +146,20 @@ export function clickExecuteNode() {
|
|||
getExecuteNodeButton().click();
|
||||
}
|
||||
|
||||
export function clickResourceLocatorInput(paramName: string) {
|
||||
getResourceLocatorInput(paramName).click();
|
||||
}
|
||||
|
||||
export function setParameterInputByName(name: string, value: string) {
|
||||
getParameterInputByName(name).clear().type(value);
|
||||
}
|
||||
|
||||
export function toggleParameterCheckboxInputByName(name: string) {
|
||||
getParameterInputByName(name).find('input[type="checkbox"]').realClick();
|
||||
export function checkParameterCheckboxInputByName(name: string) {
|
||||
getParameterInputByName(name).find('input[type="checkbox"]').check({ force: true });
|
||||
}
|
||||
|
||||
export function uncheckParameterCheckboxInputByName(name: string) {
|
||||
getParameterInputByName(name).find('input[type="checkbox"]').uncheck({ force: true });
|
||||
}
|
||||
|
||||
export function setParameterSelectByContent(name: string, content: string) {
|
||||
|
@ -127,3 +171,86 @@ export function changeOutputRunSelector(runName: string) {
|
|||
getOutputRunSelector().click();
|
||||
getVisibleSelect().find('.el-select-dropdown__item').contains(runName).click();
|
||||
}
|
||||
|
||||
export function addItemToFixedCollection(collectionName: string) {
|
||||
getFixedCollection(collectionName).getByTestId('fixed-collection-add').click();
|
||||
}
|
||||
|
||||
export function typeIntoFixedCollectionItem(collectionName: string, index: number, value: string) {
|
||||
getFixedCollection(collectionName).within(() =>
|
||||
cy.getByTestId('parameter-input').eq(index).type(value),
|
||||
);
|
||||
}
|
||||
|
||||
export function selectResourceLocatorItem(
|
||||
resourceLocator: string,
|
||||
index: number,
|
||||
expectedText: string,
|
||||
) {
|
||||
clickResourceLocatorInput(resourceLocator);
|
||||
|
||||
getVisiblePopper().findChildByTestId('rlc-item').eq(0).should('exist');
|
||||
getVisiblePopper()
|
||||
.findChildByTestId('rlc-item')
|
||||
.eq(index)
|
||||
.find('span')
|
||||
.should('contain.text', expectedText)
|
||||
.click();
|
||||
}
|
||||
|
||||
export function clickWorkflowCardContent(workflowName: string) {
|
||||
getWorkflowCardContent(workflowName).click();
|
||||
}
|
||||
|
||||
export function assertNodeOutputHintExists() {
|
||||
getNodeOutputHint().should('exist');
|
||||
}
|
||||
|
||||
export function assertNodeOutputErrorMessageExists() {
|
||||
return getNodeOutputErrorMessage().should('exist');
|
||||
}
|
||||
|
||||
// Note that this only validates the expectedContent is *included* in the output table
|
||||
export function assertOutputTableContent(expectedContent: unknown[][]) {
|
||||
for (const [i, row] of expectedContent.entries()) {
|
||||
for (const [j, value] of row.entries()) {
|
||||
// + 1 to skip header
|
||||
getOutputTbodyCell(1 + i, j).should('have.text', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function populateMapperFields(fields: ReadonlyArray<[string, string]>) {
|
||||
for (const [name, value] of fields) {
|
||||
getParameterInputByName(name).type(value);
|
||||
|
||||
// Click on a parent to dismiss the pop up which hides the field below.
|
||||
getParameterInputByName(name).parent().parent().parent().click('topLeft');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate multiValue fixedCollections. Only supports fixedCollections for which all fields can be defined via keyboard typing
|
||||
*
|
||||
* @param items - 2D array of items to populate, i.e. [["myField1", "String"], ["myField2", "Number"]]
|
||||
* @param collectionName - name of the fixedCollection to populate
|
||||
* @param offset - amount of 'parameter-input's before start, e.g. from a controlling dropdown that makes the fields appear
|
||||
* @returns
|
||||
*/
|
||||
export function populateFixedCollection<T extends readonly string[]>(
|
||||
items: readonly T[],
|
||||
collectionName: string,
|
||||
offset: number = 0,
|
||||
) {
|
||||
if (items.length === 0) return;
|
||||
const n = items[0].length;
|
||||
for (const [i, params] of items.entries()) {
|
||||
addItemToFixedCollection(collectionName);
|
||||
for (const [j, param] of params.entries()) {
|
||||
getFixedCollection(collectionName)
|
||||
.getByTestId('parameter-input')
|
||||
.eq(offset + i * n + j)
|
||||
.type(`${param}{downArrow}{enter}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
15
cypress/composables/workflowsPage.ts
Normal file
15
cypress/composables/workflowsPage.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Getters
|
||||
*/
|
||||
|
||||
export function getWorkflowsPageUrl() {
|
||||
return '/home/workflows';
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
|
||||
export function visitWorkflowsPage() {
|
||||
cy.visit(getWorkflowsPageUrl());
|
||||
}
|
|
@ -28,7 +28,7 @@ import {
|
|||
clickGetBackToCanvas,
|
||||
getRunDataInfoCallout,
|
||||
getOutputPanelTable,
|
||||
toggleParameterCheckboxInputByName,
|
||||
checkParameterCheckboxInputByName,
|
||||
} from '../composables/ndv';
|
||||
import {
|
||||
addLanguageModelNodeToParent,
|
||||
|
@ -97,7 +97,7 @@ describe('Langchain Integration', () => {
|
|||
it('should add nodes to all Agent node input types', () => {
|
||||
addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true);
|
||||
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
||||
toggleParameterCheckboxInputByName('hasOutputParser');
|
||||
checkParameterCheckboxInputByName('hasOutputParser');
|
||||
clickGetBackToCanvas();
|
||||
|
||||
addLanguageModelNodeToParent(
|
||||
|
|
|
@ -94,7 +94,7 @@ describe('Workflow Selector Parameter', () => {
|
|||
.findChildByTestId('rlc-item')
|
||||
.eq(0)
|
||||
.find('span')
|
||||
.should('have.text', 'Create a new sub-workflow');
|
||||
.should('contain.text', 'Create a'); // Due to some inconsistency we're sometimes in a project and sometimes not, this covers both cases
|
||||
|
||||
getVisiblePopper().findChildByTestId('rlc-item').eq(0).click();
|
||||
|
||||
|
|
|
@ -1,60 +1,226 @@
|
|||
import { clickGetBackToCanvas, getOutputTableHeaders } from '../composables/ndv';
|
||||
import {
|
||||
addItemToFixedCollection,
|
||||
assertNodeOutputHintExists,
|
||||
clickExecuteNode,
|
||||
clickGetBackToCanvas,
|
||||
getExecuteNodeButton,
|
||||
getOutputTableHeaders,
|
||||
getParameterInputByName,
|
||||
populateFixedCollection,
|
||||
selectResourceLocatorItem,
|
||||
typeIntoFixedCollectionItem,
|
||||
clickWorkflowCardContent,
|
||||
assertOutputTableContent,
|
||||
populateMapperFields,
|
||||
getNodeRunInfoStale,
|
||||
assertNodeOutputErrorMessageExists,
|
||||
checkParameterCheckboxInputByName,
|
||||
uncheckParameterCheckboxInputByName,
|
||||
} from '../composables/ndv';
|
||||
import {
|
||||
clickExecuteWorkflowButton,
|
||||
clickZoomToFit,
|
||||
navigateToNewWorkflowPage,
|
||||
openNode,
|
||||
pasteWorkflow,
|
||||
saveWorkflowOnButtonClick,
|
||||
} from '../composables/workflow';
|
||||
import { visitWorkflowsPage } from '../composables/workflowsPage';
|
||||
import SUB_WORKFLOW_INPUTS from '../fixtures/Test_Subworkflow-Inputs.json';
|
||||
import { NDV, WorkflowsPage, WorkflowPage } from '../pages';
|
||||
import { errorToast, successToast } from '../pages/notifications';
|
||||
import { getVisiblePopper } from '../utils';
|
||||
|
||||
const ndv = new NDV();
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const workflow = new WorkflowPage();
|
||||
|
||||
const DEFAULT_WORKFLOW_NAME = 'My workflow';
|
||||
const DEFAULT_SUBWORKFLOW_NAME_1 = 'My Sub-Workflow 1';
|
||||
const DEFAULT_SUBWORKFLOW_NAME_2 = 'My Sub-Workflow 2';
|
||||
|
||||
type FieldRow = readonly string[];
|
||||
|
||||
const exampleFields = [
|
||||
const EXAMPLE_FIELDS = [
|
||||
['aNumber', 'Number'],
|
||||
['aString', 'String'],
|
||||
['aArray', 'Array'],
|
||||
['aObject', 'Object'],
|
||||
['aAny', 'Allow Any Type'],
|
||||
// bool last since it's not an inputField so we'll skip it for some cases
|
||||
// bool last because it's a switch instead of a normal inputField so we'll skip it for some cases
|
||||
['aBool', 'Boolean'],
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Populate multiValue fixedCollections. Only supports fixedCollections for which all fields can be defined via keyboard typing
|
||||
*
|
||||
* @param items - 2D array of items to populate, i.e. [["myField1", "String"], [""]
|
||||
* @param collectionName - name of the fixedCollection to populate
|
||||
* @param offset - amount of 'parameter-input's before the fixedCollection under test
|
||||
* @returns
|
||||
*/
|
||||
function populateFixedCollection(
|
||||
items: readonly FieldRow[],
|
||||
collectionName: string,
|
||||
offset: number,
|
||||
) {
|
||||
if (items.length === 0) return;
|
||||
const n = items[0].length;
|
||||
for (const [i, params] of items.entries()) {
|
||||
ndv.actions.addItemToFixedCollection(collectionName);
|
||||
for (const [j, param] of params.entries()) {
|
||||
ndv.getters
|
||||
.fixedCollectionParameter(collectionName)
|
||||
.getByTestId('parameter-input')
|
||||
.eq(offset + i * n + j)
|
||||
.type(`${param}{downArrow}{enter}`);
|
||||
}
|
||||
type TypeField = 'Allow Any Type' | 'String' | 'Number' | 'Boolean' | 'Array' | 'Object';
|
||||
|
||||
describe('Sub-workflow creation and typed usage', () => {
|
||||
beforeEach(() => {
|
||||
navigateToNewWorkflowPage();
|
||||
pasteWorkflow(SUB_WORKFLOW_INPUTS);
|
||||
saveWorkflowOnButtonClick();
|
||||
clickZoomToFit();
|
||||
|
||||
openNode('Execute Workflow');
|
||||
|
||||
// Prevent sub-workflow from opening in new window
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'open').callsFake((url) => {
|
||||
cy.visit(url);
|
||||
});
|
||||
});
|
||||
selectResourceLocatorItem('workflowId', 0, 'Create a');
|
||||
// **************************
|
||||
// NAVIGATE TO CHILD WORKFLOW
|
||||
// **************************
|
||||
|
||||
openNode('Workflow Input Trigger');
|
||||
});
|
||||
|
||||
it('works with type-checked values', () => {
|
||||
populateFixedCollection(EXAMPLE_FIELDS, 'workflowInputs', 1);
|
||||
|
||||
validateAndReturnToParent(
|
||||
DEFAULT_SUBWORKFLOW_NAME_1,
|
||||
1,
|
||||
EXAMPLE_FIELDS.map((f) => f[0]),
|
||||
);
|
||||
|
||||
const values = [
|
||||
'-1', // number fields don't support `=` switch to expression, so let's test the Fixed case with it
|
||||
...EXAMPLE_FIELDS.slice(1).map((x) => `={{}{{} $json.a${x[0]}`), // the `}}` at the end are added automatically
|
||||
];
|
||||
|
||||
// this matches with the pinned data provided in the fixture
|
||||
populateMapperFields(values.map((x, i) => [EXAMPLE_FIELDS[i][0], x]));
|
||||
|
||||
clickExecuteNode();
|
||||
|
||||
const expected = [
|
||||
['-1', 'A String', '0:11:true2:3', 'aKey:-1', '[empty object]', 'false'],
|
||||
['-1', 'Another String', '[empty array]', 'aDifferentKey:-1', '[empty array]', 'false'],
|
||||
];
|
||||
assertOutputTableContent(expected);
|
||||
|
||||
// Test the type-checking options
|
||||
populateMapperFields([['aString', '{selectAll}{backspace}{{}{{} 5']]);
|
||||
|
||||
getNodeRunInfoStale().should('exist');
|
||||
clickExecuteNode();
|
||||
|
||||
assertNodeOutputErrorMessageExists();
|
||||
|
||||
// attemptToConvertTypes enabled
|
||||
checkParameterCheckboxInputByName('attemptToConvertTypes');
|
||||
|
||||
getNodeRunInfoStale().should('exist');
|
||||
clickExecuteNode();
|
||||
|
||||
const expected2 = [
|
||||
['-1', '5', '0:11:true2:3', 'aKey:-1', '[empty object]', 'false'],
|
||||
['-1', '5', '[empty array]', 'aDifferentKey:-1', '[empty array]', 'false'],
|
||||
];
|
||||
|
||||
assertOutputTableContent(expected2);
|
||||
|
||||
// disabled again
|
||||
uncheckParameterCheckboxInputByName('attemptToConvertTypes');
|
||||
|
||||
getNodeRunInfoStale().should('exist');
|
||||
clickExecuteNode();
|
||||
|
||||
assertNodeOutputErrorMessageExists();
|
||||
});
|
||||
|
||||
it('works with Fields input source, and can then be changed to JSON input source', () => {
|
||||
assertNodeOutputHintExists();
|
||||
|
||||
populateFixedCollection(EXAMPLE_FIELDS, 'workflowInputs', 1);
|
||||
|
||||
validateAndReturnToParent(
|
||||
DEFAULT_SUBWORKFLOW_NAME_1,
|
||||
1,
|
||||
EXAMPLE_FIELDS.map((f) => f[0]),
|
||||
);
|
||||
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'open').callsFake((url) => {
|
||||
cy.visit(url);
|
||||
});
|
||||
});
|
||||
selectResourceLocatorItem('workflowId', 0, 'Create a');
|
||||
|
||||
openNode('Workflow Input Trigger');
|
||||
|
||||
getParameterInputByName('inputSource').click();
|
||||
|
||||
getVisiblePopper()
|
||||
.getByTestId('parameter-input')
|
||||
.eq(0)
|
||||
.type('Using JSON Example{downArrow}{enter}');
|
||||
|
||||
const exampleJson =
|
||||
'{{}' + EXAMPLE_FIELDS.map((x) => `"${x[0]}": ${makeExample(x[1])}`).join(',') + '}';
|
||||
getParameterInputByName('jsonExample')
|
||||
.find('.cm-line')
|
||||
.eq(0)
|
||||
.type(`{selectAll}{backspace}${exampleJson}{enter}`);
|
||||
|
||||
// first one doesn't work for some reason, might need to wait for something?
|
||||
clickExecuteNode();
|
||||
|
||||
validateAndReturnToParent(
|
||||
DEFAULT_SUBWORKFLOW_NAME_2,
|
||||
2,
|
||||
EXAMPLE_FIELDS.map((f) => f[0]),
|
||||
);
|
||||
|
||||
assertOutputTableContent([
|
||||
['[null]', '[null]', '[null]', '[null]', '[null]', 'false'],
|
||||
['[null]', '[null]', '[null]', '[null]', '[null]', 'false'],
|
||||
]);
|
||||
|
||||
clickExecuteNode();
|
||||
});
|
||||
|
||||
it('should show node issue when no fields are defined in manual mode', () => {
|
||||
getExecuteNodeButton().should('be.disabled');
|
||||
clickGetBackToCanvas();
|
||||
// Executing the workflow should show an error toast
|
||||
clickExecuteWorkflowButton();
|
||||
errorToast().should('contain', 'The workflow has issues');
|
||||
openNode('Workflow Input Trigger');
|
||||
// Add a field to the workflowInputs fixedCollection
|
||||
addItemToFixedCollection('workflowInputs');
|
||||
typeIntoFixedCollectionItem('workflowInputs', 0, 'test');
|
||||
// Executing the workflow should not show error now
|
||||
clickGetBackToCanvas();
|
||||
clickExecuteWorkflowButton();
|
||||
successToast().should('contain', 'Workflow executed successfully');
|
||||
});
|
||||
});
|
||||
|
||||
// This function starts off in the Child Workflow Input Trigger, assuming we just defined the input fields
|
||||
// It then navigates back to the parent and validates the outputPanel matches our changes
|
||||
function validateAndReturnToParent(targetChild: string, offset: number, fields: string[]) {
|
||||
clickExecuteNode();
|
||||
|
||||
// + 1 to account for formatting-only column
|
||||
getOutputTableHeaders().should('have.length', fields.length + 1);
|
||||
for (const [i, name] of fields.entries()) {
|
||||
getOutputTableHeaders().eq(i).should('have.text', name);
|
||||
}
|
||||
|
||||
clickGetBackToCanvas();
|
||||
saveWorkflowOnButtonClick();
|
||||
|
||||
visitWorkflowsPage();
|
||||
|
||||
clickWorkflowCardContent(DEFAULT_WORKFLOW_NAME);
|
||||
|
||||
openNode('Execute Workflow');
|
||||
|
||||
// Note that outside of e2e tests this will be pre-selected correctly.
|
||||
// Due to our workaround to remain in the same tab we need to select the correct tab manually
|
||||
selectResourceLocatorItem('workflowId', offset, targetChild);
|
||||
|
||||
clickExecuteNode();
|
||||
|
||||
getOutputTableHeaders().should('have.length', fields.length + 1);
|
||||
for (const [i, name] of fields.entries()) {
|
||||
getOutputTableHeaders().eq(i).should('have.text', name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,215 +240,3 @@ function makeExample(type: TypeField) {
|
|||
return 'null';
|
||||
}
|
||||
}
|
||||
|
||||
type TypeField = 'Allow Any Type' | 'String' | 'Number' | 'Boolean' | 'Array' | 'Object';
|
||||
function populateFields(items: ReadonlyArray<readonly [string, TypeField]>) {
|
||||
populateFixedCollection(items, 'workflowInputs', 1);
|
||||
}
|
||||
|
||||
function navigateWorkflowSelectionDropdown(index: number, expectedText: string) {
|
||||
ndv.getters.resourceLocator('workflowId').should('be.visible');
|
||||
ndv.getters.resourceLocatorInput('workflowId').click();
|
||||
|
||||
getVisiblePopper().findChildByTestId('rlc-item').eq(0).should('exist');
|
||||
getVisiblePopper()
|
||||
.findChildByTestId('rlc-item')
|
||||
.eq(index)
|
||||
.find('span')
|
||||
.should('have.text', expectedText)
|
||||
.click();
|
||||
}
|
||||
|
||||
function populateMapperFields(values: readonly string[], offset: number) {
|
||||
for (const [i, value] of values.entries()) {
|
||||
cy.getByTestId('parameter-input')
|
||||
.eq(offset + i)
|
||||
.type(value);
|
||||
|
||||
// Click on a parent to dismiss the pop up hiding the field below.
|
||||
cy.getByTestId('parameter-input')
|
||||
.eq(offset + i)
|
||||
.parent()
|
||||
.parent()
|
||||
.click('topLeft');
|
||||
}
|
||||
}
|
||||
|
||||
// This function starts off in the Child Workflow Input Trigger, assuming we just defined the input fields
|
||||
// It then navigates back to the parent and validates output
|
||||
function validateAndReturnToParent(targetChild: string, offset: number, fields: string[]) {
|
||||
ndv.actions.execute();
|
||||
|
||||
// + 1 to account for formatting-only column
|
||||
getOutputTableHeaders().should('have.length', fields.length + 1);
|
||||
for (const [i, name] of fields.entries()) {
|
||||
getOutputTableHeaders().eq(i).should('have.text', name);
|
||||
}
|
||||
|
||||
clickGetBackToCanvas();
|
||||
saveWorkflowOnButtonClick();
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
workflowsPage.getters.workflowCardContent(DEFAULT_WORKFLOW_NAME).click();
|
||||
|
||||
openNode('Execute Workflow');
|
||||
|
||||
// Note that outside of e2e tests this will be pre-selected correctly.
|
||||
// Due to our workaround to remain in the same tab we need to select the correct tab manually
|
||||
navigateWorkflowSelectionDropdown(offset, targetChild);
|
||||
|
||||
// This fails, pointing to `usePushConnection` `const triggerNode = subWorkflow?.nodes.find` being `undefined.find()`I <think>
|
||||
ndv.actions.execute();
|
||||
|
||||
getOutputTableHeaders().should('have.length', fields.length + 1);
|
||||
for (const [i, name] of fields.entries()) {
|
||||
getOutputTableHeaders().eq(i).should('have.text', name);
|
||||
}
|
||||
|
||||
// todo: verify the fields appear and show the correct types
|
||||
|
||||
// todo: fill in the input fields (and mock previous node data in the json fixture to match)
|
||||
|
||||
// todo: validate the actual output data
|
||||
}
|
||||
|
||||
function setWorkflowInputFieldValue(index: number, value: string) {
|
||||
ndv.actions.addItemToFixedCollection('workflowInputs');
|
||||
ndv.actions.typeIntoFixedCollectionItem('workflowInputs', index, value);
|
||||
}
|
||||
|
||||
describe('Sub-workflow creation and typed usage', () => {
|
||||
beforeEach(() => {
|
||||
navigateToNewWorkflowPage();
|
||||
pasteWorkflow(SUB_WORKFLOW_INPUTS);
|
||||
saveWorkflowOnButtonClick();
|
||||
clickZoomToFit();
|
||||
|
||||
openNode('Execute Workflow');
|
||||
|
||||
// Prevent sub-workflow from opening in new window
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'open').callsFake((url) => {
|
||||
cy.visit(url);
|
||||
});
|
||||
});
|
||||
navigateWorkflowSelectionDropdown(0, 'Create a new sub-workflow');
|
||||
// **************************
|
||||
// NAVIGATE TO CHILD WORKFLOW
|
||||
// **************************
|
||||
|
||||
openNode('Workflow Input Trigger');
|
||||
});
|
||||
|
||||
it('works with type-checked values', () => {
|
||||
populateFields(exampleFields);
|
||||
|
||||
validateAndReturnToParent(
|
||||
DEFAULT_SUBWORKFLOW_NAME_1,
|
||||
1,
|
||||
exampleFields.map((f) => f[0]),
|
||||
);
|
||||
|
||||
const values = [
|
||||
'-1', // number fields don't support `=` switch to expression, so let's test the Fixed case with it
|
||||
...exampleFields.slice(1).map((x) => `={{}{{} $json.a${x[0]}`), // }} are added automatically
|
||||
];
|
||||
|
||||
// this matches with the pinned data provided in the fixture
|
||||
populateMapperFields(values, 2);
|
||||
|
||||
ndv.actions.execute();
|
||||
|
||||
// todo:
|
||||
// - validate output lines up
|
||||
// - change input to need casts
|
||||
// - run
|
||||
// - confirm error
|
||||
// - switch `attemptToConvertTypes` flag
|
||||
// - confirm success and changed output
|
||||
// - change input to be invalid despite cast
|
||||
// - run
|
||||
// - confirm error
|
||||
// - switch type option flags
|
||||
// - run
|
||||
// - confirm success
|
||||
// - turn off attempt to cast flag
|
||||
// - confirm a value was not cast
|
||||
});
|
||||
|
||||
it('works with Fields input source into JSON input source', () => {
|
||||
ndv.getters.nodeOutputHint().should('exist');
|
||||
|
||||
populateFields(exampleFields);
|
||||
|
||||
validateAndReturnToParent(
|
||||
DEFAULT_SUBWORKFLOW_NAME_1,
|
||||
1,
|
||||
exampleFields.map((f) => f[0]),
|
||||
);
|
||||
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'open').callsFake((url) => {
|
||||
cy.visit(url);
|
||||
});
|
||||
});
|
||||
navigateWorkflowSelectionDropdown(0, 'Create a new sub-workflow');
|
||||
|
||||
openNode('Workflow Input Trigger');
|
||||
|
||||
cy.getByTestId('parameter-input').eq(0).click();
|
||||
|
||||
// Todo: Check if there's a better way to interact with option dropdowns
|
||||
// This PR would add this child testId
|
||||
getVisiblePopper()
|
||||
.getByTestId('parameter-input')
|
||||
.eq(0)
|
||||
.type('Using JSON Example{downArrow}{enter}');
|
||||
|
||||
const exampleJson =
|
||||
'{{}' + exampleFields.map((x) => `"${x[0]}": ${makeExample(x[1])}`).join(',') + '}';
|
||||
cy.getByTestId('parameter-input-jsonExample')
|
||||
.find('.cm-line')
|
||||
.eq(0)
|
||||
.type(`{selectAll}{backspace}${exampleJson}{enter}`);
|
||||
|
||||
// first one doesn't work for some reason, might need to wait for something?
|
||||
ndv.actions.execute();
|
||||
|
||||
validateAndReturnToParent(
|
||||
DEFAULT_SUBWORKFLOW_NAME_2,
|
||||
2,
|
||||
exampleFields.map((f) => f[0]),
|
||||
);
|
||||
|
||||
// test for either InputSource mode and options combinations:
|
||||
// + we're showing the notice in the output panel
|
||||
// + we start with no fields
|
||||
// + Test Step works and we create the fields
|
||||
// + create field of each type (string, number, boolean, object, array, any)
|
||||
// + exit ndv
|
||||
// + save
|
||||
// + go back to parent workflow
|
||||
// - verify fields appear [needs Ivan's PR]
|
||||
// - link fields [needs Ivan's PR]
|
||||
// + run parent
|
||||
// - verify output with `null` defaults exists
|
||||
//
|
||||
});
|
||||
|
||||
it('should show node issue when no fields are defined in manual mode', () => {
|
||||
ndv.getters.nodeExecuteButton().should('be.disabled');
|
||||
ndv.actions.close();
|
||||
// Executing the workflow should show an error toast
|
||||
workflow.actions.executeWorkflow();
|
||||
errorToast().should('contain', 'The workflow has issues');
|
||||
openNode('Workflow Input Trigger');
|
||||
// Add a field to the workflowInputs fixedCollection
|
||||
setWorkflowInputFieldValue(0, 'test');
|
||||
// Executing the workflow should not show error now
|
||||
ndv.actions.close();
|
||||
workflow.actions.executeWorkflow();
|
||||
successToast().should('contain', 'Workflow executed successfully');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue