super-productivity/e2e/tests/sync/supersync-worklog.spec.ts
Johannes Millan fc7c8e517c test(e2e): reduce supersync test flakiness
- Replace page.reload() with page.goto() pattern throughout sync tests
  (service workers cause reload timeouts)
- Add handling for "Encryption Settings Will Change" dialog in import tests
- Add page reload after sync to ensure archive data is loaded in worklog test
- Add skipSpinnerCheck option for cases where sync may have already completed
- Improve waits and selectors in error-handling tests for better stability
- Make snackbar visibility check non-blocking in wrong-password test

Reduces flaky tests from 6 to 1 in supersync test suite.
2026-01-26 13:48:10 +01:00

343 lines
12 KiB
TypeScript

import { test, expect } from '../../fixtures/supersync.fixture';
import {
createTestUser,
getSuperSyncConfig,
createSimulatedClient,
closeClient,
markTaskDone,
type SimulatedE2EClient,
} from '../../utils/supersync-helpers';
import { waitForAppReady } from '../../utils/waits';
/**
* SuperSync Worklog/Archive E2E Tests
*
* Tests worklog and archive query scenarios:
* - Archive tasks, query on other client
* - Worklog data structure sync
* - Multiple archives across clients
*/
test.describe('@supersync Worklog Sync', () => {
/**
* Test: Archived tasks appear in worklog on synced client
*
* Actions:
* 1. Client A creates and completes tasks
* 2. Client A archives via Finish Day
* 3. Client A syncs
* 4. Client B syncs
* 5. Client B verifies tasks in worklog
*/
test('Archived tasks appear in worklog on synced client', async ({
browser,
baseURL,
testRunId,
}) => {
const uniqueId = Date.now();
let clientA: SimulatedE2EClient | null = null;
let clientB: SimulatedE2EClient | null = null;
try {
const user = await createTestUser(testRunId);
const syncConfig = getSuperSyncConfig(user);
// ============ PHASE 1: Client A Creates and Completes Tasks ============
clientA = await createSimulatedClient(browser, baseURL!, 'A', testRunId);
await clientA.sync.setupSuperSync(syncConfig);
const task1Name = `Worklog1-${uniqueId}`;
const task2Name = `Worklog2-${uniqueId}`;
await clientA.workView.addTask(task1Name);
await clientA.workView.addTask(task2Name);
console.log('[Worklog Test] Client A created 2 tasks');
// Mark both as done
await markTaskDone(clientA, task1Name);
await markTaskDone(clientA, task2Name);
console.log('[Worklog Test] Client A marked tasks done');
// ============ PHASE 2: Client A Archives ============
const finishDayBtn = clientA.page.locator('.e2e-finish-day');
await finishDayBtn.waitFor({ state: 'visible', timeout: 10000 });
await finishDayBtn.click();
await clientA.page.waitForURL(/daily-summary/, { timeout: 10000 });
await clientA.page.waitForLoadState('networkidle');
const saveBtn = clientA.page.locator(
'daily-summary button[mat-flat-button]:has(mat-icon:has-text("wb_sunny"))',
);
await saveBtn.waitFor({ state: 'visible', timeout: 10000 });
await saveBtn.click();
await clientA.page.waitForURL(/tag\/TODAY/, { timeout: 10000 });
console.log('[Worklog Test] Client A archived tasks');
// ============ PHASE 3: Sync ============
await clientA.sync.syncAndWait();
console.log('[Worklog Test] Client A synced');
// ============ PHASE 4: Client B Downloads and Checks Worklog ============
clientB = await createSimulatedClient(browser, baseURL!, 'B', testRunId);
await clientB.sync.setupSuperSync(syncConfig);
await clientB.sync.syncAndWait();
console.log('[Worklog Test] Client B synced');
// Navigate to worklog
await clientB.page.goto('/#/tag/TODAY/worklog');
await clientB.page.waitForLoadState('networkidle');
await clientB.page.waitForSelector('worklog', { timeout: 10000 });
console.log('[Worklog Test] Client B navigated to worklog');
// Expand week to see tasks
const weekRow = clientB.page.locator('.week-row').first();
if (await weekRow.isVisible()) {
await weekRow.click();
await clientB.page.waitForTimeout(500);
}
// Verify both tasks appear in worklog
const task1InWorklog = clientB.page.locator(
`.task-summary-table .task-title:has-text("${task1Name}")`,
);
const task2InWorklog = clientB.page.locator(
`.task-summary-table .task-title:has-text("${task2Name}")`,
);
await expect(task1InWorklog).toBeVisible({ timeout: 10000 });
await expect(task2InWorklog).toBeVisible({ timeout: 10000 });
console.log('[Worklog Test] Both tasks visible in worklog on Client B');
} finally {
if (clientA) await closeClient(clientA);
if (clientB) await closeClient(clientB);
}
});
/**
* Test: Worklog shows correct time data after sync
*
* Actions:
* 1. Client A creates task, tracks time, completes
* 2. Client A archives
* 3. Client B syncs and checks worklog for time data
*/
test('Worklog shows correct time data after sync', async ({
browser,
baseURL,
testRunId,
}) => {
const uniqueId = Date.now();
let clientA: SimulatedE2EClient | null = null;
let clientB: SimulatedE2EClient | null = null;
try {
const user = await createTestUser(testRunId);
const syncConfig = getSuperSyncConfig(user);
// ============ PHASE 1: Client A Creates, Tracks, Completes ============
clientA = await createSimulatedClient(browser, baseURL!, 'A', testRunId);
await clientA.sync.setupSuperSync(syncConfig);
const taskName = `TimedWorklog-${uniqueId}`;
await clientA.workView.addTask(taskName);
// Track time using helper functions (need to add imports)
const task = clientA.page.locator(`task:has-text("${taskName}")`);
await task.hover();
await task.locator('.start-task-btn').click();
await clientA.page.waitForTimeout(3000);
await task.hover();
const pauseBtn = task.locator('button:has(mat-icon:has-text("pause"))');
await pauseBtn.click();
console.log('[Timed Worklog Test] Tracked 3 seconds');
// Complete task
await markTaskDone(clientA, taskName);
// ============ PHASE 2: Archive ============
const finishDayBtn = clientA.page.locator('.e2e-finish-day');
await finishDayBtn.waitFor({ state: 'visible', timeout: 10000 });
await finishDayBtn.click();
await clientA.page.waitForURL(/daily-summary/, { timeout: 10000 });
const saveBtn = clientA.page.locator(
'daily-summary button[mat-flat-button]:has(mat-icon:has-text("wb_sunny"))',
);
await saveBtn.waitFor({ state: 'visible', timeout: 10000 });
await saveBtn.click();
await clientA.page.waitForURL(/tag\/TODAY/, { timeout: 10000 });
// ============ PHASE 3: Sync ============
await clientA.sync.syncAndWait();
// ============ PHASE 4: Client B Checks Worklog ============
clientB = await createSimulatedClient(browser, baseURL!, 'B', testRunId);
await clientB.sync.setupSuperSync(syncConfig);
await clientB.sync.syncAndWait();
await clientB.page.goto('/#/tag/TODAY/worklog');
await clientB.page.waitForLoadState('networkidle');
await clientB.page.waitForSelector('worklog', { timeout: 10000 });
// Expand week
const weekRow = clientB.page.locator('.week-row').first();
if (await weekRow.isVisible()) {
await weekRow.click();
await clientB.page.waitForTimeout(500);
}
// Verify task appears in worklog
const taskInWorklog = clientB.page.locator(`text=${taskName}`);
await expect(taskInWorklog).toBeVisible({ timeout: 10000 });
// The task with tracked time should appear in the worklog
// Time data is stored but exact UI format varies
console.log('[Timed Worklog Test] Task with time data visible in worklog');
} finally {
if (clientA) await closeClient(clientA);
if (clientB) await closeClient(clientB);
}
});
/**
* Test: Multiple archive operations sync correctly
*
* Actions:
* 1. Client A creates and archives tasks day 1
* 2. Client A syncs
* 3. Client B syncs
* 4. Client B creates and archives more tasks
* 5. Both sync
* 6. Verify all archived tasks in worklog on both
*/
test('Multiple archive operations sync correctly', async ({
browser,
baseURL,
testRunId,
}) => {
const uniqueId = Date.now();
let clientA: SimulatedE2EClient | null = null;
let clientB: SimulatedE2EClient | null = null;
try {
const user = await createTestUser(testRunId);
const syncConfig = getSuperSyncConfig(user);
// ============ PHASE 1: Client A Archives First Batch ============
clientA = await createSimulatedClient(browser, baseURL!, 'A', testRunId);
await clientA.sync.setupSuperSync(syncConfig);
const taskA1 = `BatchA1-${uniqueId}`;
await clientA.workView.addTask(taskA1);
await markTaskDone(clientA, taskA1);
// Archive
const finishDayBtnA = clientA.page.locator('.e2e-finish-day');
await finishDayBtnA.click();
await clientA.page.waitForURL(/daily-summary/, { timeout: 10000 });
const saveBtnA = clientA.page.locator(
'daily-summary button[mat-flat-button]:has(mat-icon:has-text("wb_sunny"))',
);
await saveBtnA.click();
await clientA.page.waitForURL(/tag\/TODAY/, { timeout: 10000 });
console.log('[Multi Archive Test] Client A archived first batch');
await clientA.sync.syncAndWait();
// ============ PHASE 2: Client B Archives Second Batch ============
clientB = await createSimulatedClient(browser, baseURL!, 'B', testRunId);
await clientB.sync.setupSuperSync(syncConfig);
await clientB.sync.syncAndWait();
const taskB1 = `BatchB1-${uniqueId}`;
await clientB.workView.addTask(taskB1);
await markTaskDone(clientB, taskB1);
// Archive
const finishDayBtnB = clientB.page.locator('.e2e-finish-day');
await finishDayBtnB.click();
await clientB.page.waitForURL(/daily-summary/, { timeout: 10000 });
const saveBtnB = clientB.page.locator(
'daily-summary button[mat-flat-button]:has(mat-icon:has-text("wb_sunny"))',
);
await saveBtnB.click();
await clientB.page.waitForURL(/tag\/TODAY/, { timeout: 10000 });
console.log('[Multi Archive Test] Client B archived second batch');
// ============ PHASE 3: Sync Both ============
await clientB.sync.syncAndWait();
await clientA.sync.syncAndWait();
console.log('[Multi Archive Test] Both synced');
// Reload both clients to ensure archive data from sync is loaded
// Archive data is stored in a separate IndexedDB store and may need reload to appear in UI
await clientA.page.goto(clientA.page.url(), {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
await waitForAppReady(clientA.page);
await clientB.page.goto(clientB.page.url(), {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
await waitForAppReady(clientB.page);
console.log('[Multi Archive Test] Both clients reloaded after sync');
// ============ PHASE 4: Verify All Archives in Worklog ============
// Check Client A worklog
await clientA.page.goto('/#/tag/TODAY/worklog');
await clientA.page.waitForLoadState('networkidle');
await clientA.page.waitForSelector('worklog', { timeout: 10000 });
const weekRowA = clientA.page.locator('.week-row').first();
if (await weekRowA.isVisible()) {
await weekRowA.click();
await clientA.page.waitForTimeout(500);
}
const taskA1InWorklog = clientA.page.locator(
`.task-summary-table .task-title:has-text("${taskA1}")`,
);
const taskB1InWorklogA = clientA.page.locator(
`.task-summary-table .task-title:has-text("${taskB1}")`,
);
await expect(taskA1InWorklog).toBeVisible({ timeout: 10000 });
await expect(taskB1InWorklogA).toBeVisible({ timeout: 10000 });
console.log('[Multi Archive Test] Client A has both archives in worklog');
// Check Client B worklog
await clientB.page.goto('/#/tag/TODAY/worklog');
await clientB.page.waitForLoadState('networkidle');
await clientB.page.waitForSelector('worklog', { timeout: 10000 });
const weekRowB = clientB.page.locator('.week-row').first();
if (await weekRowB.isVisible()) {
await weekRowB.click();
await clientB.page.waitForTimeout(500);
}
const taskA1InWorklogB = clientB.page.locator(
`.task-summary-table .task-title:has-text("${taskA1}")`,
);
const taskB1InWorklog = clientB.page.locator(
`.task-summary-table .task-title:has-text("${taskB1}")`,
);
await expect(taskA1InWorklogB).toBeVisible({ timeout: 10000 });
await expect(taskB1InWorklog).toBeVisible({ timeout: 10000 });
console.log('[Multi Archive Test] Client B has both archives in worklog');
} finally {
if (clientA) await closeClient(clientA);
if (clientB) await closeClient(clientB);
}
});
});