chore: [DHIS2-22074] de-flake the RelatedStages, assignee and sharing dialog scenarios - #4718
chore: [DHIS2-22074] de-flake the RelatedStages, assignee and sharing dialog scenarios#4718karolinelien wants to merge 6 commits into
Conversation
The two link and unlink scenarios assert that the schedule and enter details actions are disabled. Those actions are disabled only when the Baby Postnatal stage already has an event, since useCanAddNewEventToStage returns repeatable || existingRelatedEvents.length === 0 and both Child Programme stages are non-repeatable. Neither scenario creates that event, so both depend on one being left behind by an earlier run. For enrollment EOxeNf2MdBf that dependency is circular: the next scenario in the file deletes every event of the same enrollment and re-creates them through the UI, so when it fails part way through, the following run fails in the link and unlink scenario instead, with an input that never becomes disabled. That is the shard 2 flake. Seed the event through the API instead, guarded by a check for an existing one, which is also the only case where creating it is legal for a non-repeatable stage. Both link and unlink scenarios now state the fixture they need rather than inheriting it. Also make the two event deletion steps idempotent: the reload only happened when there was something to delete, so a re-attempt kept the pre-deletion page, and neither step noticed an import that came back with errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The seed guard checked only that a Baby Postnatal event exists. Both link-and-unlink scenarios then pick that event out of the "Link to an existing event" list, which the widget builds from linkableEvents — events not already carrying a relationship of that type. A run that dies between the Link click and the closing unlink leaves the event linked, so the next run would skip seeding on existence alone and fail on an empty list. The step now also strips the event's relationships, through the same code path as the unlink step, and is renamed to say so. Collapse the four tracker writes in the file onto one importTracker helper that asserts status === 'OK', replacing three near-identical inline blocks and covering the unlink step, which had no assertion. Seed with CREATE rather than CREATE_AND_UPDATE, since the guard has already established the event is absent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two Enroll trackedEntity scenarios created a real tracked entity and deleted it again through the UI as their last step, so a failure anywhere earlier in the scenario leaked the record - once per retry attempt. Move that cleanup into an After hook that deletes through the API, which runs even when the scenario fails, following the pattern from #4714. The generated first name is captured in a module variable so the hook can look the record up, and uses Date.now() so parallel shards cannot match each other's records. Also drop the linkability half of the seed guard. It was added for a state that turns out to be unreachable: deleting an event cascades to its relationships, verified directly against play dev - link a Birth event to its Baby Postnatal event, delete the Birth event, and the related event comes back with zero relationships. So a run that dies mid-scenario cannot leave a Baby Postnatal event that is present but unlinkable; the Birth deletion in the New event scenario and the existing unlink step in the Edit event scenario each clear it. The guard is back to checking existence, which is the state the assertion actually reads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both assignee scenarios assign a user and remove them again as their last step, so a run that dies in between leaves the event assigned. The Assign button only renders when nobody is assigned - DisplayMode.component.tsx branches on `assignee`, showing Edit instead - so every later run fails looking for `widget-assignee-assign`, on every branch, until someone clears the assignment by hand. That is the state the shared dev instance is in now: the same failure reproduces on other open pull requests. Add `you make sure the event <id> has no assigned user`, which clears the assignment through the API by re-importing the event without `assignedUser`. Verified against play stable-2-43-1: with Geetha assigned to z4PCgdaBXFh, the spec passes 12/12 with the guard and fails with the exact CI assertion without it. Both scenarios get the guard - the edit mode one leaks the same way and has only been lucky so far. Move the tracker import helper out of RelatedStages.js into a cy.importTracker command in cypress/support/commands.js, since the guard needs it too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`you change the sharing settings` clicked "Share view" and typed into the
dialog's search box with nothing in between. The box is disabled while the
dialog loads the object's sharing state, and `cy.type()` does not wait for
that to clear - the CI error carries no "Timed out retrying" prefix, so it
threw immediately rather than after the command timeout. `cy.get` retried
only until the input existed, which it does while still disabled.
Gate it with a retrying `should('be.enabled')` and scope the lookup to the
dialog, the way the newer WorkingListsSharing step already does - the bare
`[placeholder="Search"]` would match any search box on the page.
This is the failure seen on `cypress (2.42, 5)` and, on another branch, on
`cypress (2.43, 5)` - same scenario, same error, different version, so a race
rather than anything version specific.
Verified against play stable-2-43-1: both sharing scenarios pass with the
gate in place.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
| .join('/'), | ||
| ); | ||
|
|
||
| Cypress.Commands.add('importTracker', (importStrategy, payload) => |
There was a problem hiding this comment.
Not sure about introducing cy.importTracker here. We already have ~20 inline tracker-import call sites across the specs, and none use a helper today.
Two concerns:
- Using the helper only for these 6 new sites leaves us half-migrated and creates two patterns.
expect(body.status).to.eq('OK')adds a new invariant that other tracker imports don’t assert, which feels separate from this de-flake PR.
I recommend inlining these 6 to match the existing style, and instead migrate all ~25 call sites in a broader readfactor later.
| const { assignedUser, ...event } = body; | ||
|
|
||
| if (!assignedUser) { | ||
| return undefined; |
There was a problem hiding this comment.
Explicit assignedUser: null over field omission — makes the "clear" intent legible at the callsite.
| import { When, Then } from '@badeball/cypress-cucumber-preprocessor'; | ||
| import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'; | ||
|
|
||
| const EVENT_FIELDS = 'event,program,programStage,enrollment,orgUnit,occurredAt,scheduledAt,status,dataValues'; |
There was a problem hiding this comment.
Drop the EVENT_FIELDS allowlist and fetch the whole event body — matches the existing GET-then-UPDATE pattern (e.g. EnrollmentEditEventPageForm.js) and removes the drift risk of missing a field if the event shape gains a required one later.
| ) | ||
| .then(url => cy.request(url)) | ||
| .then(({ body }) => { | ||
| const apiTrackedEntities = body.trackedEntities || body.instances || []; |
There was a problem hiding this comment.
Fallback for body.instances is for pre-v40 tracker responses.



https://dhis2.atlassian.net/browse/DHIS2-22074
Six scenarios that depend on state no step establishes, plus one race.
Assignbutton renders only when nobody is assigned, so one interrupted run leaves the shared instance stuck for every branch. New step clears the assignment.cy.type()does not wait for that to clear. Gated withshould('be.enabled')and scoped to the dialog.Also: the
Enroll trackedEntityscenarios clean up through an APIAfterhook instead of the UI, so it runs when a scenario fails; the two deletion steps always reload; and the tracker writes share acy.importTrackercommand that asserts the import succeeded.Verified against play stable-2-43-1, each run starting from the broken state — including the control: with the assignee guard removed, the CI failure reproduces exactly.
Not fixed here:
The sharing settings on a program stage working list view survive an update(2.41) — one sample, no CI artifacts, and it sits in a file the working-list sharing branch is actively changing.AI Assisted.