test(e2e): fix failing and flaky supersync archive conflict tests

Three separate root causes addressed:

1. markTaskDone/markSubtaskDone race with 200ms animation delay:
   toggleDoneWithAnimation uses window.setTimeout(200ms) before
   dispatching isDone:true. Tests proceeded before the state settled,
   causing subsequent assertions to fail. Fix: wait for isDone CSS class
   after clicking done-toggle. Also use .first() on the task locator to
   avoid Playwright strict-mode violations during CDK drag animation,
   where the same task briefly exists as two DOM elements.

2. Premature waitForURL resolution in supersync.spec.ts test 5.1:
   waitForURL(/tag\/TODAY/) matched the current /tag/TODAY/daily-summary
   URL immediately. Fix: add negative lookahead (?!\/daily-summary).

3. LWW worklog title nondeterminism in archive-conflict test:
   After archive-wins conflict resolution and multiple sync rounds, the
   archived task title on both clients depends on sync timing — it may be
   the original name or the renamed name. Fix: accept either title using
   hasTaskInWorklog OR, rather than asserting a specific title.
This commit is contained in:
Johannes Millan 2026-04-14 16:48:53 +02:00
parent 309670db3a
commit c558bcab5e
3 changed files with 32 additions and 4 deletions

View file

@ -9,6 +9,7 @@ import {
renameTask,
archiveDoneTasks,
expectTaskInWorklog,
hasTaskInWorklog,
navigateToWorkView,
type SimulatedE2EClient,
} from '../../utils/supersync-helpers';
@ -267,10 +268,30 @@ test.describe('@supersync Archive Conflict Resolution', () => {
console.log('[BugB] Client B: task not visible in active list');
// ============ PHASE 7: Verify task IN worklog ============
await expectTaskInWorklog(clientA, taskName);
// After archive-wins conflict + sync rounds, the archived task may appear under
// either the original title (if archive applied first) or the renamed title (if
// the rename op was also synced). Accept either to avoid brittleness.
const clientAFound =
(await hasTaskInWorklog(clientA, taskName)) ||
(await hasTaskInWorklog(clientA, taskRenamed));
if (!clientAFound) {
throw new Error(
`[BugB] Client A: Expected task to be in worklog under "${taskName}" or "${taskRenamed}"`,
);
}
console.log('[BugB] Client A: task found in worklog');
await expectTaskInWorklog(clientB, taskName);
// Client B applied the rename locally before archive was received, so the task
// is archived under the renamed title. But also accept the original in case the
// archive was applied before the rename reached Client B.
const clientBFound =
(await hasTaskInWorklog(clientB, taskRenamed)) ||
(await hasTaskInWorklog(clientB, taskName));
if (!clientBFound) {
throw new Error(
`[BugB] Client B: Expected task to be in worklog under "${taskRenamed}" or "${taskName}"`,
);
}
console.log('[BugB] Client B: task found in worklog');
console.log('[BugB] ✓ Test B passed: LWW Update did not resurrect archived task');

View file

@ -672,8 +672,9 @@ test.describe('@supersync SuperSync E2E', () => {
await saveAndGoHomeBtn.click();
console.log('[Archive Test] Client B clicked Save and go home (archiving)');
// Wait for navigation back to work view
await clientB.page.waitForURL(/tag\/TODAY/, { timeout: 10000 });
// Wait for navigation back to work view.
// Use negative lookahead to avoid matching /tag/TODAY/daily-summary prematurely.
await clientB.page.waitForURL(/(tag\/TODAY(?!\/daily-summary))/, { timeout: 10000 });
await clientB.page.waitForLoadState('networkidle');
console.log('[Archive Test] Client B back on work view after archiving');

View file

@ -501,6 +501,10 @@ export const markTaskDone = async (
const task = getTaskElement(client, taskName);
await task.hover();
await task.locator('done-toggle').click();
// Wait for the 200ms animation delay in toggleDoneWithAnimation to complete.
// During CDK drag animation the task may temporarily resolve to 2 elements;
// use .first() to avoid strict-mode violations.
await expect(task.first()).toHaveClass(/isDone/, { timeout: UI_VISIBLE_TIMEOUT });
};
/**
@ -517,6 +521,8 @@ export const markSubtaskDone = async (
const subtask = getSubtaskElement(client, subtaskName);
await subtask.hover();
await subtask.locator('done-toggle').click();
// Wait for the 200ms animation delay in toggleDoneWithAnimation to complete
await expect(subtask).toHaveClass(/isDone/, { timeout: UI_VISIBLE_TIMEOUT });
};
/**