diff --git a/frontend/cypress/tests/workflow-manager.cy.js b/frontend/cypress/tests/workflow-manager.cy.js new file mode 100644 index 0000000..9ea2626 --- /dev/null +++ b/frontend/cypress/tests/workflow-manager.cy.js @@ -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'); + }); + }); +});