Skip to content

Commit e4658dc

Browse files
Add program schema support and payroll E2E tests
Pass beneficiary JSON schema during program creation so that advanced criteria fields (educated_level, able_bodied, etc.) are available in payment plan and payroll forms. Harden login command with cookie clearing and increased timeout. Add initial payroll spec and command helpers.
1 parent f56469b commit e4658dc

7 files changed

Lines changed: 401 additions & 6 deletions

File tree

cypress/e2e/payment-plan.cy.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,27 @@ describe('Payment plan workflows', () => {
1515
const groupProgramCode = `PPGP${Date.now().toString().slice(-4)}`;
1616
const groupProgramName = `E2E Payment Plan Group ${suiteTimestamp}`;
1717
const maxBeneficiaries = '50';
18+
const beneficiarySchema = {
19+
$id: 'https://example.com/beneficiares.schema.json',
20+
type: 'object',
21+
title: 'Program Schema for Beneficiaries',
22+
$schema: 'http://json-schema.org/draft-04/schema#',
23+
properties: {
24+
able_bodied: {
25+
type: 'boolean',
26+
description: 'Flag determining whether someone is able bodied or not',
27+
},
28+
educated_level: {
29+
type: 'string',
30+
description: 'The level of person when it comes to the school/education/studies',
31+
},
32+
number_of_children: {
33+
type: 'integer',
34+
description: 'Number of children',
35+
},
36+
},
37+
description: 'This document records the details beneficiares',
38+
};
1839
const createdPaymentPlans = new Set();
1940

2041
const planData = (label, benefitPlanName) => {
@@ -26,7 +47,9 @@ describe('Payment plan workflows', () => {
2647
name: `E2E Payment Plan ${label} ${timestamp}`,
2748
benefitPlanName,
2849
dateValidFrom: getDateOffset(0),
29-
dateValidTo: getDateOffset(30),
50+
// dateValidTo is intentionally omitted — it is optional for payment plans
51+
// and the MUI DatePicker dialog overwrites typed values with "today",
52+
// causing dateValidTo == dateValidFrom which hides plans from the list.
3053
};
3154
};
3255

@@ -42,7 +65,7 @@ describe('Payment plan workflows', () => {
4265
cy.logoutAdminInterface();
4366

4467
cy.login();
45-
cy.createProgram(individualProgramCode, individualProgramName, maxBeneficiaries, 'INDIVIDUAL');
68+
cy.createProgram(individualProgramCode, individualProgramName, maxBeneficiaries, 'INDIVIDUAL', beneficiarySchema);
4669
cy.createProgram(groupProgramCode, groupProgramName, maxBeneficiaries, 'GROUP');
4770
cy.logout();
4871
});
@@ -99,8 +122,7 @@ describe('Payment plan workflows', () => {
99122
cy.get('[title="Save changes"] button').should('not.be.disabled');
100123

101124
cy.enterMuiInput('Code', existingPlan.code);
102-
cy.get('[title="Please fill General Information fields first"] button', { timeout: 10000 })
103-
.should('be.disabled');
125+
cy.get('[title="Save changes"] button').should('be.disabled');
104126
});
105127

106128
it('applies advanced criteria from the program JSON schema', () => {

cypress/e2e/payroll.cy.js

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { getTimestamp } from '../support/utils';
2+
3+
describe('Payroll workflows', () => {
4+
const suiteTimestamp = getTimestamp();
5+
const getDateOffset = (days) => {
6+
const date = new Date();
7+
date.setDate(date.getDate() + days);
8+
const day = String(date.getDate()).padStart(2, '0');
9+
const month = String(date.getMonth() + 1).padStart(2, '0');
10+
const year = date.getFullYear();
11+
return `${day}-${month}-${year}`;
12+
};
13+
14+
// Codes must be ≤ 8 characters (backend limit for program/payment-plan codes).
15+
const ts = Date.now();
16+
const programCode = `PR${ts.toString().slice(-6)}`;
17+
const programName = `E2E Payroll Program ${suiteTimestamp}`;
18+
const ppCode = `PP${ts.toString().slice(-6)}`;
19+
const ppName = `E2E Payroll Plan ${suiteTimestamp}`;
20+
// Payment cycle codes have no documented 8-char limit; use a longer unique value.
21+
const cycleCode = `PCY${ts.toString().slice(-5)}`;
22+
23+
const createdPayrolls = new Set();
24+
25+
const payrollData = (label) => {
26+
const timestamp = getTimestamp();
27+
return {
28+
name: `E2E Payroll ${label} ${timestamp}`,
29+
paymentPlanCode: ppCode,
30+
paymentPlanName: ppName,
31+
paymentCycleCode: cycleCode,
32+
dateValidFrom: getDateOffset(0),
33+
dateValidTo: getDateOffset(30),
34+
// paymentMethod is omitted → first available method is selected
35+
};
36+
};
37+
38+
const trackPayroll = (name) => {
39+
createdPayrolls.add(name);
40+
};
41+
42+
before(() => {
43+
cy.loginAdminInterface();
44+
cy.setModuleConfig('fe-core', 'menu-config-sp.json');
45+
cy.setModuleConfig('social_protection', 'social-protection-config.json');
46+
cy.setModuleConfig('individual', 'individual-config-minimal.json');
47+
cy.logoutAdminInterface();
48+
49+
cy.login();
50+
cy.createProgram(programCode, programName, '50', 'INDIVIDUAL');
51+
cy.createPaymentPlan({
52+
code: ppCode,
53+
name: ppName,
54+
benefitPlanName: programName,
55+
dateValidFrom: getDateOffset(0),
56+
dateValidTo: getDateOffset(365),
57+
});
58+
// The PaymentCyclePicker in the payroll form searches only ACTIVE cycles.
59+
// Creating with status ACTIVE triggers a coreAlert dialog; dismiss it below.
60+
cy.createPaymentCycle({
61+
code: cycleCode,
62+
startDate: getDateOffset(0),
63+
endDate: getDateOffset(30),
64+
status: 'ACTIVE',
65+
});
66+
cy.get('body').then(($body) => {
67+
if ($body.find('[role="dialog"]').length > 0) {
68+
cy.get('[role="dialog"] .MuiDialogActions-root button').first().click();
69+
}
70+
});
71+
cy.logout();
72+
});
73+
74+
after(() => {
75+
cy.login();
76+
// Only PENDING_APPROVAL payrolls have an enabled delete button in the UI.
77+
// Tests that change the status are responsible for their own cleanup.
78+
Array.from(createdPayrolls).forEach((name) => {
79+
cy.deletePayrollFromList(name);
80+
});
81+
cy.deletePaymentPlan(ppName);
82+
cy.deleteProgram(programName);
83+
// Payment cycles have no UI delete; cycleCode records accumulate in the DB.
84+
cy.logout();
85+
});
86+
87+
beforeEach(() => {
88+
cy.login();
89+
});
90+
91+
it('validates required fields before allowing payroll creation', () => {
92+
cy.openCreatePayroll();
93+
cy.get('[title="Please fill General Information fields first"] button')
94+
.should('be.disabled');
95+
});
96+
97+
it('creates a payroll successfully', () => {
98+
const payroll = payrollData('Create');
99+
100+
cy.createPayroll(payroll);
101+
trackPayroll(payroll.name);
102+
103+
cy.filterPayrolls({ name: payroll.name });
104+
cy.assertPayrollRowVisible({ name: payroll.name });
105+
});
106+
107+
it('searches payrolls by name', () => {
108+
const targetPayroll = payrollData('Search Target');
109+
const otherPayroll = payrollData('Search Other');
110+
111+
cy.createPayroll(targetPayroll);
112+
cy.createPayroll(otherPayroll);
113+
trackPayroll(targetPayroll.name);
114+
trackPayroll(otherPayroll.name);
115+
116+
cy.filterPayrolls({ name: targetPayroll.name });
117+
cy.assertPayrollRowVisible({ name: targetPayroll.name });
118+
cy.assertPayrollRowNotVisible({ name: otherPayroll.name });
119+
120+
cy.filterPayrolls({ name: otherPayroll.name });
121+
cy.assertPayrollRowVisible({ name: otherPayroll.name });
122+
cy.assertPayrollRowNotVisible({ name: targetPayroll.name });
123+
});
124+
125+
it('views payroll details from the list', () => {
126+
const payroll = payrollData('View');
127+
128+
cy.createPayroll(payroll);
129+
trackPayroll(payroll.name);
130+
131+
cy.openPayrollForViewFromList(payroll.name);
132+
cy.assertMuiInput('Name', payroll.name);
133+
});
134+
135+
it('deletes a PENDING_APPROVAL payroll', () => {
136+
const payroll = payrollData('Delete');
137+
138+
cy.createPayroll(payroll);
139+
// Not tracked: deleted below, so no after() cleanup needed.
140+
141+
cy.deletePayrollFromList(payroll.name);
142+
cy.filterPayrolls({ name: payroll.name });
143+
cy.assertPayrollRowNotVisible({ name: payroll.name });
144+
});
145+
146+
it('shows a newly-created payroll in the pending payrolls list', () => {
147+
const payroll = payrollData('Pending');
148+
149+
cy.createPayroll(payroll);
150+
trackPayroll(payroll.name);
151+
152+
cy.visit('/front/payrollsPending');
153+
cy.contains('Payrolls Found');
154+
cy.contains('button', 'Search').click();
155+
cy.assertPayrollRowVisible({ name: payroll.name });
156+
});
157+
158+
it('opens and closes the reconciliation summary dialog from the pending list', () => {
159+
const payroll = payrollData('Reconcile Dialog');
160+
161+
cy.createPayroll(payroll);
162+
trackPayroll(payroll.name);
163+
164+
cy.openPayrollPendingSummary(payroll.name);
165+
cy.contains('button', 'Close').click();
166+
cy.contains('View Reconciliation Summary:').should('not.exist');
167+
});
168+
});

cypress/support/commands/auth.commands.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
export function registerAuthCommands() {
22
Cypress.Commands.add('login', () => {
3+
// Clear cookies so we always land on a clean login page regardless of
4+
// the state the previous test left the browser in.
5+
cy.clearCookies();
36
cy.visit('/front/login');
47

5-
cy.get('body', { timeout: 15000 })
8+
cy.get('body', { timeout: 30000 })
69
.should(($body) => {
710
const loggedIn = $body.find('button[title="Log out"]').length > 0
811
|| $body.text().includes('Welcome Admin Admin!');
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { registerPaymentPlanCommands } from './payment-plan.commands';
22
import { registerPaymentCycleCommands } from './payment-cycle.commands';
3+
import { registerPayrollCommands } from './payroll.commands';
34

45
export function registerPaymentCommands() {
56
registerPaymentPlanCommands();
67
registerPaymentCycleCommands();
8+
registerPayrollCommands();
79
}

0 commit comments

Comments
 (0)