super-productivity/e2e/tests/sync/webdav-sync-expansion.spec.ts
Johannes Millan b1afc4eae8 test(e2e): fix failing sync tests and reduce flakiness
- Fix SuperSync add-button click interception by hovering the group
  header first (activates pointer-events) and targeting the button
  element instead of mat-icon, with force-click fallback
- Fix WebDAV sync-expansion project lookup by using the proven
  navigateToProjectByName helper instead of a fragile manual
  sidebar locator
- Reduce flakiness in rapid-sync test by adding explicit timeouts
  to post-loop verification assertions
2026-03-27 17:33:38 +01:00

183 lines
6.3 KiB
TypeScript

import { expect, test } from '../../fixtures/webdav.fixture';
import { SyncPage } from '../../pages/sync.page';
import { WorkViewPage } from '../../pages/work-view.page';
import { ProjectPage } from '../../pages/project.page';
import { waitForStatePersistence } from '../../utils/waits';
import {
createSyncFolder,
generateSyncFolderName,
setupSyncClient,
waitForSyncComplete,
WEBDAV_CONFIG_TEMPLATE,
} from '../../utils/sync-helpers';
// Timing constants for sync detection
const WEBDAV_TIMESTAMP_DELAY_MS = 2000;
test.describe('@webdav WebDAV Sync Expansion', () => {
// Run sync tests serially to avoid WebDAV server contention
test.describe.configure({ mode: 'serial' });
test('should sync projects', async ({ browser, baseURL, request }) => {
test.slow();
const SYNC_FOLDER_NAME = generateSyncFolderName('e2e-expansion-proj');
await createSyncFolder(request, SYNC_FOLDER_NAME);
const WEBDAV_CONFIG = {
...WEBDAV_CONFIG_TEMPLATE,
syncFolderPath: `/${SYNC_FOLDER_NAME}`,
};
const url = baseURL || 'http://localhost:4242';
// --- Client A ---
const { context: contextA, page: pageA } = await setupSyncClient(browser, url);
const syncPageA = new SyncPage(pageA);
const workViewPageA = new WorkViewPage(pageA);
const projectPageA = new ProjectPage(pageA);
await workViewPageA.waitForTaskList();
// Configure Sync A
await syncPageA.setupWebdavSync(WEBDAV_CONFIG);
// Create Project on A
const projectName = 'Synced Project';
await projectPageA.createProject(projectName);
// Navigate to the newly created project (createProject doesn't auto-navigate)
await projectPageA.navigateToProjectByName(projectName);
// Add task to new project on A
await workViewPageA.addTask('Task in Project A');
// Sync A
await syncPageA.triggerSync();
await waitForSyncComplete(pageA, syncPageA);
// --- Client B ---
const { context: contextB, page: pageB } = await setupSyncClient(browser, url);
const syncPageB = new SyncPage(pageB);
const workViewPageB = new WorkViewPage(pageB);
const projectPageB = new ProjectPage(pageB);
await workViewPageB.waitForTaskList();
// Configure Sync B
await syncPageB.setupWebdavSync(WEBDAV_CONFIG);
await syncPageB.triggerSync();
await waitForSyncComplete(pageB, syncPageB);
// Wait for state persistence to complete after sync
await waitForStatePersistence(pageB);
// Navigate to the synced project — navigateToProjectByName handles expanding
// the Projects group, waiting for the tree item to appear, and clicking it.
await projectPageB.navigateToProjectByName(projectName);
// Verify task
await expect(pageB.locator('task')).toHaveCount(1);
await expect(pageB.locator('task').first()).toContainText('Task in Project A');
// Add task on B in project
await workViewPageB.addTask('Task in Project B');
// Wait for state persistence before syncing
await waitForStatePersistence(pageB);
await syncPageB.triggerSync();
await waitForSyncComplete(pageB, syncPageB);
// Wait for server to process and ensure Last-Modified timestamp differs
// WebDAV servers often have second-level timestamp precision
await pageB.waitForTimeout(WEBDAV_TIMESTAMP_DELAY_MS);
// Sync A - trigger sync to download changes from B
await syncPageA.triggerSync();
await waitForSyncComplete(pageA, syncPageA);
// Wait for state persistence to complete after sync
await waitForStatePersistence(pageA);
// Navigate to project page to verify task synced
await projectPageA.navigateToProjectByName(projectName);
// Check if task B is visible immediately after sync (no reload)
await expect(pageA.locator('task', { hasText: 'Task in Project B' })).toBeVisible({
timeout: 20000,
});
await contextA.close();
await contextB.close();
});
test('should sync task done state', async ({ browser, baseURL, request }) => {
test.slow();
const SYNC_FOLDER_NAME = generateSyncFolderName('e2e-expansion-done');
await createSyncFolder(request, SYNC_FOLDER_NAME);
const WEBDAV_CONFIG = {
...WEBDAV_CONFIG_TEMPLATE,
syncFolderPath: `/${SYNC_FOLDER_NAME}`,
};
const url = baseURL || 'http://localhost:4242';
// --- Client A ---
const { context: contextA, page: pageA } = await setupSyncClient(browser, url);
const syncPageA = new SyncPage(pageA);
const workViewPageA = new WorkViewPage(pageA);
await workViewPageA.waitForTaskList();
await syncPageA.setupWebdavSync(WEBDAV_CONFIG);
const taskName = 'Task to be done';
await workViewPageA.addTask(taskName);
await syncPageA.triggerSync();
await waitForSyncComplete(pageA, syncPageA);
// --- Client B ---
const { context: contextB, page: pageB } = await setupSyncClient(browser, url);
const syncPageB = new SyncPage(pageB);
const workViewPageB = new WorkViewPage(pageB);
await workViewPageB.waitForTaskList();
// Configure Sync B
await syncPageB.setupWebdavSync(WEBDAV_CONFIG);
await syncPageB.triggerSync();
await waitForSyncComplete(pageB, syncPageB);
// Wait for state persistence to complete after sync
await waitForStatePersistence(pageB);
// Verify task synced to B
const taskB = pageB.locator('task', { hasText: taskName }).first();
await expect(taskB).toBeVisible({ timeout: 20000 });
await expect(taskB).not.toHaveClass(/isDone/);
// --- Test 1: Mark done on B, verify on A ---
await taskB.hover();
const doneBtnB = taskB.locator('done-toggle');
await doneBtnB.click({ force: true });
await expect(taskB).toHaveClass(/isDone/);
// Wait for state persistence before syncing
await waitForStatePersistence(pageB);
await syncPageB.triggerSync();
await waitForSyncComplete(pageB, syncPageB);
// Sync A to get done state from B
await syncPageA.triggerSync();
await waitForSyncComplete(pageA, syncPageA);
// Wait for state persistence to complete after sync
await waitForStatePersistence(pageA);
// Note: We DON'T reload - sync updates NgRx directly
// Verify task is marked as done on A after sync
const taskA = pageA.locator('task', { hasText: taskName }).first();
await expect(taskA).toHaveClass(/isDone/, { timeout: 10000 });
await contextA.close();
await contextB.close();
});
});