Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
379 changes: 379 additions & 0 deletions frontend/cypress/tests/workflow-manager.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,379 @@
/**
* Workflow Manager Acceptance Tests
* Issue #19: Write acceptance tests
*
* Tests cover:
* 1. Creating / Deleting a workflow
* 2. Making sure actions are working
* 3. Adding / Deleting / Updating states/transitions with permissions, groups
* 4. Sanity check
* 5. After save, the workflow has the updated items
*/

context('Workflow Manager Acceptance Tests', () => {
beforeEach(() => {
cy.viewport('macbook-16');
cy.autologin();
});

describe('1. Creating and Deleting Workflows', () => {
it('Can navigate to Workflow Manager control panel', () => {
cy.visit('/controlpanel/workflowmanager');
cy.get('body').should('contain', 'Workflow Manager');
});

it('Can create a new workflow', () => {
cy.visit('/controlpanel/workflowmanager');

// Click add workflow button
cy.get('#toolbar-add-workflow, [id*="add-workflow"], button').contains(/add/i).click();

// Fill in workflow details
cy.get('input[name="title"], input[id*="title"]').type('Test Workflow');
cy.get('input[name="id"], input[id*="id"]').type('test_workflow');
cy.get('textarea[name="description"], textarea[id*="description"]').type('A test workflow for acceptance testing');

// Submit the form
cy.get('button[type="submit"], button').contains(/create|save|add/i).click();

// Verify workflow was created
cy.get('body').should('contain', 'Test Workflow');
});

it('Can delete an existing workflow', () => {
// First create a workflow to delete
cy.visit('/controlpanel/workflowmanager');

// Find and click on a workflow (or the test workflow we created)
cy.get('[data-workflow-id], .workflow-item, [class*="workflow"]')
.first()
.click();

// Look for delete action
cy.get('button').contains(/delete/i).click();

// Confirm deletion
cy.get('button').contains(/confirm|yes|ok/i).click();

// Verify deletion message or that workflow is removed
cy.get('body').should('contain', /deleted|removed|success/i);
});
});

describe('2. Workflow Actions', () => {
beforeEach(() => {
cy.visit('/controlpanel/workflowmanager');
// Navigate to an existing workflow
cy.get('[data-workflow-id], .workflow-item, a[href*="workflow"]')
.first()
.click();
});

it('Can add a new state', () => {
// Click Add State button
cy.get('button').contains(/add state/i).click();

// Fill in state details
cy.get('input[name="title"], input[id*="title"]').type('Draft State');
cy.get('input[name="id"], input[id*="id"]').type('draft_state');

// Submit
cy.get('button[type="submit"], button').contains(/create|save|add/i).click();

// Verify state appears in graph or sidebar
cy.get('body').should('contain', 'Draft State');
});

it('Can add a new transition', () => {
// Click Add Transition button
cy.get('button').contains(/add transition/i).click();

// Fill in transition details
cy.get('input[name="title"], input[id*="title"]').type('Publish');
cy.get('input[name="id"], input[id*="id"]').type('publish');

// Select source and destination states
cy.get('[name="source_state"], [id*="source"]').click();
cy.get('[role="option"], [class*="option"]').first().click();

cy.get('[name="destination_state"], [id*="destination"]').click();
cy.get('[role="option"], [class*="option"]').last().click();

// Submit
cy.get('button[type="submit"], button').contains(/create|save|add/i).click();

// Verify transition was created
cy.get('body').should('contain', 'Publish');
});

it('Sanity Check button works', () => {
// Click Sanity Check button
cy.get('button').contains(/sanity check/i).click();

// Wait for validation to complete
cy.get('body').should('satisfy', ($body) => {
const text = $body.text().toLowerCase();
return text.includes('validation') ||
text.includes('success') ||
text.includes('error') ||
text.includes('check');
});
});

it('Assign button opens assign dialog', () => {
// Click Assign button
cy.get('button').contains(/assign/i).click();

// Verify dialog opens
cy.get('[role="dialog"], [class*="modal"], [class*="dialog"]').should('be.visible');

// Close dialog
cy.get('button').contains(/cancel|close/i).click();
});
});

describe('3. States and Transitions with Permissions and Groups', () => {
beforeEach(() => {
cy.visit('/controlpanel/workflowmanager');
cy.get('[data-workflow-id], .workflow-item, a[href*="workflow"]')
.first()
.click();
});

it('Can update state permissions', () => {
// Click on States tab in sidebar
cy.get('[role="tab"]').contains(/states/i).click();

// Select a state
cy.get('[class*="state-item"], [data-state-id]').first().click();

// Look for permissions section
cy.get('body').should('contain', /permission/i);

// Toggle a permission checkbox if available
cy.get('input[type="checkbox"]').first().click();

// Save changes
cy.get('#toolbar-saving-workflow, button').contains(/save/i).click();

// Verify save success
cy.get('.Toastify, [class*="toast"]').should('contain', /success|saved/i);
});

it('Can update transition permissions and groups', () => {
// Click on Transitions tab in sidebar
cy.get('[role="tab"]').contains(/transitions/i).click();

// Select a transition
cy.get('[class*="transition-item"], [data-transition-id]').first().click();

// Check for permissions section
cy.get('body').then(($body) => {
if ($body.text().toLowerCase().includes('permission')) {
// Toggle permission if exists
cy.get('input[type="checkbox"]').first().click();
}
});

// Save changes
cy.get('#toolbar-saving-workflow, button').contains(/save/i).click();

// Verify save success
cy.get('.Toastify, [class*="toast"]').should('contain', /success|saved/i);
});

it('Can delete a state', () => {
// Click on States tab
cy.get('[role="tab"]').contains(/states/i).click();

// Select a state
cy.get('[class*="state-item"], [data-state-id]').first().click();

// Click delete button
cy.get('button').contains(/delete/i).click();

// Confirm deletion
cy.get('button').contains(/confirm|yes|ok/i).click();

// Verify deletion message
cy.get('.Toastify, [class*="toast"]').should('contain', /deleted|success/i);
});

it('Can delete a transition', () => {
// Click on Transitions tab
cy.get('[role="tab"]').contains(/transitions/i).click();

// Select a transition
cy.get('[class*="transition-item"], [data-transition-id]').first().click();

// Click delete button
cy.get('button').contains(/delete/i).click();

// Confirm deletion
cy.get('button').contains(/confirm|yes|ok/i).click();

// Verify deletion message
cy.get('.Toastify, [class*="toast"]').should('contain', /deleted|success/i);
});
});

describe('4. Sanity Check Validation', () => {
beforeEach(() => {
cy.visit('/controlpanel/workflowmanager');
cy.get('[data-workflow-id], .workflow-item, a[href*="workflow"]')
.first()
.click();
});

it('Sanity check validates workflow correctly', () => {
// Run sanity check
cy.get('button').contains(/sanity check/i).click();

// Should show either success or error toast
cy.get('.Toastify, [class*="toast"]', { timeout: 10000 }).should('be.visible');
});

it('View Results button appears when there are validation errors', () => {
// Run sanity check
cy.get('button').contains(/sanity check/i).click();

// Check if View Results link appears (only on errors)
cy.get('body').then(($body) => {
if ($body.text().toLowerCase().includes('view results')) {
cy.get('button, a').contains(/view results/i).should('be.visible');
}
});
});
});

describe('5. Saving and Persistence', () => {
const testWorkflowTitle = 'Persistence Test Workflow ' + Date.now();

beforeEach(() => {
cy.visit('/controlpanel/workflowmanager');
});

it('Changes persist after saving', () => {
// Navigate to a workflow
cy.get('[data-workflow-id], .workflow-item, a[href*="workflow"]')
.first()
.click();

// Click on Workflow tab
cy.get('[role="tab"]').contains(/workflow/i).click();

// Update the workflow title/description
cy.get('input[name="title"], input[id*="title"]')
.clear()
.type('Updated Workflow Title ' + Date.now());

// Save
cy.get('#toolbar-saving-workflow, button').contains(/save/i).click();

// Wait for success toast
cy.get('.Toastify, [class*="toast"]').should('contain', /success|saved/i);

// Reload the page
cy.reload();

// Verify the updated title persists
cy.get('body').should('contain', 'Updated Workflow Title');
});

it('State updates persist after save', () => {
// Navigate to a workflow
cy.get('[data-workflow-id], .workflow-item, a[href*="workflow"]')
.first()
.click();

// Click on States tab
cy.get('[role="tab"]').contains(/states/i).click();

// Select a state
cy.get('[class*="state-item"], [data-state-id]').first().click();

// Get the current state title and modify it
const updatedStateTitle = 'Updated State ' + Date.now();
cy.get('input[name="title"], input[id*="title"]')
.clear()
.type(updatedStateTitle);

// Save
cy.get('#toolbar-saving-workflow, button').contains(/save/i).click();

// Wait for success toast
cy.get('.Toastify, [class*="toast"]').should('contain', /success|saved/i);

// Reload and verify
cy.reload();
cy.get('[role="tab"]').contains(/states/i).click();
cy.get('body').should('contain', 'Updated State');
});

it('Transition updates persist after save', () => {
// Navigate to a workflow
cy.get('[data-workflow-id], .workflow-item, a[href*="workflow"]')
.first()
.click();

// Click on Transitions tab
cy.get('[role="tab"]').contains(/transitions/i).click();

// Select a transition
cy.get('[class*="transition-item"], [data-transition-id]').first().click();

// Update the transition title
const updatedTransitionTitle = 'Updated Transition ' + Date.now();
cy.get('input[name="title"], input[id*="title"]')
.clear()
.type(updatedTransitionTitle);

// Save
cy.get('#toolbar-saving-workflow, button').contains(/save/i).click();

// Wait for success toast
cy.get('.Toastify, [class*="toast"]').should('contain', /success|saved/i);

// Reload and verify
cy.reload();
cy.get('[role="tab"]').contains(/transitions/i).click();
cy.get('body').should('contain', 'Updated Transition');
});
});

describe('Graph Interactions', () => {
beforeEach(() => {
cy.visit('/controlpanel/workflowmanager');
cy.get('[data-workflow-id], .workflow-item, a[href*="workflow"]')
.first()
.click();
});

it('Clicking a node in the graph selects the state', () => {
// Click on a node in the React Flow graph
cy.get('.react-flow__node').first().click();

// States tab should be selected in sidebar
cy.get('[role="tab"][aria-selected="true"]').should('contain', /states/i);
});

it('Clicking an edge in the graph selects the transition', () => {
// Click on an edge in the React Flow graph
cy.get('.react-flow__edge').first().click();

// Transitions tab should be selected in sidebar
cy.get('[role="tab"][aria-selected="true"]').should('contain', /transitions/i);
});

it('Graph controls are visible and functional', () => {
// Zoom controls should be visible
cy.get('.react-flow__controls').should('be.visible');

// Minimap should be visible
cy.get('.react-flow__minimap').should('be.visible');

// Background should be visible
cy.get('.react-flow__background').should('be.visible');
});
});
});