perf(e2e): cache WebDAV health checks at worker level

Optimize WebDAV E2E test startup by caching health checks at the
worker level instead of checking in each test file's beforeAll hook.

Changes:
- Create webdav.fixture.ts with worker-level health check caching
- Update 12 WebDAV test files to use new fixture
- Remove redundant beforeAll health check blocks

Impact:
- Reduces health check overhead from ~8s to ~2s when WebDAV unavailable
- Saves ~6 seconds on PR/build CI runs (combined with existing SuperSync optimization)
- Tests automatically skip when WebDAV server is not reachable
This commit is contained in:
Johannes Millan 2026-01-21 19:16:58 +01:00
parent 3a9d35149d
commit 867b708413
13 changed files with 98 additions and 120 deletions

View file

@ -0,0 +1,85 @@
import { test as base } from '@playwright/test';
import { isWebDavServerUp } from '../utils/check-webdav';
/**
* Extended test fixture for WebDAV E2E tests.
*
* Provides:
* - Automatic server health check (skips tests if server unavailable)
* - Worker-level caching to avoid redundant health checks
*
* This reduces health check overhead from ~8s to ~2s when WebDAV is unavailable
* by checking once per worker instead of once per test file.
*
* Usage:
* ```typescript
* import { test, expect } from '../../fixtures/webdav.fixture';
*
* test.describe('@webdav My Tests', () => {
* test('should sync', async ({ page, request }) => {
* // Server health already checked, test will skip if unavailable
* });
* });
* ```
*/
export interface WebDavFixtures {
/** Whether the WebDAV server is reachable and available */
webdavServerUp: boolean;
}
// Cache server health check result per worker to avoid repeated checks
let serverHealthCache: boolean | null = null;
export const test = base.extend<WebDavFixtures>({
/**
* Check WebDAV server health once per worker and cache the result.
* Tests are automatically skipped if the server is not reachable.
*/
webdavServerUp: async ({}, use, testInfo) => {
// Only check once per worker
if (serverHealthCache === null) {
serverHealthCache = await isWebDavServerUp();
if (!serverHealthCache) {
console.warn(
'WebDAV server not reachable at http://127.0.0.1:2345/ - skipping tests',
);
}
}
// Skip the test if server is not reachable
testInfo.skip(!serverHealthCache, 'WebDAV server not reachable');
await use(serverHealthCache);
},
});
/**
* Helper to create a describe block that auto-checks WebDAV server health.
* Use this instead of manually adding beforeAll health checks.
*
* @param title - Test suite title (will be prefixed with @webdav)
* @param fn - Test suite function
*
* @example
* ```typescript
* webdavDescribe('Archive Sync', () => {
* test('should sync archive', async ({ page, webdavServerUp }) => {
* // Server health already checked
* });
* });
* ```
*/
export const webdavDescribe = (title: string, fn: () => void): void => {
test.describe(`@webdav ${title}`, () => {
// The webdavServerUp fixture will auto-skip if server unavailable
test.beforeEach(async ({ webdavServerUp }) => {
// This line ensures the fixture is evaluated and test is skipped if needed
void webdavServerUp;
});
fn();
});
};
// Re-export expect for convenience
export { expect } from '@playwright/test';

View file

@ -1,8 +1,7 @@
import { test, expect } from '../../fixtures/test.fixture';
import { test, expect } from '../../fixtures/webdav.fixture';
import { SyncPage } from '../../pages/sync.page';
import { WorkViewPage } from '../../pages/work-view.page';
import { waitForStatePersistence } from '../../utils/waits';
import { isWebDavServerUp } from '../../utils/check-webdav';
import {
WEBDAV_CONFIG_TEMPLATE,
setupSyncClient,
@ -24,13 +23,6 @@ import { waitForAppReady } from '../../utils/waits';
test.describe('WebDAV First Sync Conflict', () => {
test.describe.configure({ mode: 'serial' });
test.beforeAll(async () => {
const isUp = await isWebDavServerUp(WEBDAV_CONFIG_TEMPLATE.baseUrl);
if (!isUp) {
test.skip(true, 'WebDAV server not reachable');
}
});
test('should show conflict dialog and allow USE_LOCAL to upload local data', async ({
browser,
baseURL,

View file

@ -1,8 +1,7 @@
import { test, expect } from '../../fixtures/test.fixture';
import { test, expect } from '../../fixtures/webdav.fixture';
import { SyncPage } from '../../pages/sync.page';
import { WorkViewPage } from '../../pages/work-view.page';
import { waitForStatePersistence } from '../../utils/waits';
import { isWebDavServerUp } from '../../utils/check-webdav';
import {
WEBDAV_CONFIG_TEMPLATE,
createSyncFolder,
@ -34,14 +33,6 @@ import legacyDataCollisionB from '../../fixtures/legacy-migration-collision-b.js
test.describe('@migration WebDAV Legacy Migration Sync', () => {
test.describe.configure({ mode: 'serial' });
test.beforeAll(async () => {
const isUp = await isWebDavServerUp(WEBDAV_CONFIG_TEMPLATE.baseUrl);
if (!isUp) {
console.warn('WebDAV server not reachable. Skipping WebDAV tests.');
test.skip(true, 'WebDAV server not reachable');
}
});
/**
* Test: Both clients migrated from legacy - Keep local resolution
*

View file

@ -1,8 +1,7 @@
import { test, expect } from '../../fixtures/test.fixture';
import { test, expect } from '../../fixtures/webdav.fixture';
import { SyncPage } from '../../pages/sync.page';
import { WorkViewPage } from '../../pages/work-view.page';
import { waitForStatePersistence } from '../../utils/waits';
import { isWebDavServerUp } from '../../utils/check-webdav';
import {
WEBDAV_CONFIG_TEMPLATE,
setupSyncClient,
@ -34,14 +33,6 @@ test.describe('@webdav WebDAV Provider Switch', () => {
syncFolderPath: `/${SYNC_FOLDER_NAME}`,
};
test.beforeAll(async () => {
const isUp = await isWebDavServerUp(WEBDAV_CONFIG_TEMPLATE.baseUrl);
if (!isUp) {
console.warn('WebDAV server not reachable. Skipping WebDAV tests.');
test.skip(true, 'WebDAV server not reachable');
}
});
test('should sync tasks when Client B connects to existing WebDAV server (provider switch)', async ({
browser,
baseURL,

View file

@ -1,7 +1,6 @@
import { test, expect } from '../../fixtures/test.fixture';
import { test, expect } from '../../fixtures/webdav.fixture';
import { SyncPage } from '../../pages/sync.page';
import { WorkViewPage } from '../../pages/work-view.page';
import { isWebDavServerUp } from '../../utils/check-webdav';
import {
WEBDAV_CONFIG_TEMPLATE,
setupSyncClient,
@ -14,14 +13,6 @@ test.describe('WebDAV Sync Advanced Features', () => {
// Run sync tests serially to avoid WebDAV server contention
test.describe.configure({ mode: 'serial' });
test.beforeAll(async () => {
const isUp = await isWebDavServerUp(WEBDAV_CONFIG_TEMPLATE.baseUrl);
if (!isUp) {
console.warn('WebDAV server not reachable. Skipping WebDAV tests.');
test.skip(true, 'WebDAV server not reachable');
}
});
test('should sync sub-tasks correctly', async ({ browser, baseURL, request }) => {
const SYNC_FOLDER_NAME = generateSyncFolderName('e2e-advanced-sub');
await createSyncFolder(request, SYNC_FOLDER_NAME);

View file

@ -1,10 +1,9 @@
import { expect, Page } from '@playwright/test';
import { test } from '../../fixtures/test.fixture';
import { Page } from '@playwright/test';
import { test, expect } from '../../fixtures/webdav.fixture';
import { SyncPage } from '../../pages/sync.page';
import { WorkViewPage } from '../../pages/work-view.page';
import { TaskPage } from '../../pages/task.page';
import { waitForStatePersistence } from '../../utils/waits';
import { isWebDavServerUp } from '../../utils/check-webdav';
import {
WEBDAV_CONFIG_TEMPLATE,
setupSyncClient,
@ -57,14 +56,6 @@ test.describe('@webdav WebDAV Archive Sync', () => {
// Run sync tests serially to avoid WebDAV server contention
test.describe.configure({ mode: 'serial' });
test.beforeAll(async () => {
const isUp = await isWebDavServerUp(WEBDAV_CONFIG_TEMPLATE.baseUrl);
if (!isUp) {
console.warn('WebDAV server not reachable. Skipping WebDAV archive tests.');
test.skip(true, 'WebDAV server not reachable');
}
});
/**
* Scenario 1: Two clients archive different tasks
*

View file

@ -1,12 +1,11 @@
import { expect } from '@playwright/test';
import { test } from '../../fixtures/test.fixture';
import { test } from '../../fixtures/webdav.fixture';
import { SyncPage } from '../../pages/sync.page';
import { WorkViewPage } from '../../pages/work-view.page';
import { TaskPage } from '../../pages/task.page';
import { TagPage } from '../../pages/tag.page';
import { ProjectPage } from '../../pages/project.page';
import { waitForStatePersistence, waitForAppReady } from '../../utils/waits';
import { isWebDavServerUp } from '../../utils/check-webdav';
import {
WEBDAV_CONFIG_TEMPLATE,
setupSyncClient,
@ -51,14 +50,6 @@ const archiveDoneTasks = async (page: Page): Promise<void> => {
test.describe('@webdav WebDAV Delete Cascade Sync', () => {
test.describe.configure({ mode: 'serial' });
test.beforeAll(async () => {
const isUp = await isWebDavServerUp(WEBDAV_CONFIG_TEMPLATE.baseUrl);
if (!isUp) {
console.warn('WebDAV server not reachable. Skipping WebDAV delete cascade tests.');
test.skip(true, 'WebDAV server not reachable');
}
});
/**
* Test 1.1: Delete tag with archived tasks syncs to other client
*

View file

@ -1,7 +1,6 @@
import { test, expect } from '../../fixtures/test.fixture';
import { test, expect } from '../../fixtures/webdav.fixture';
import { SyncPage } from '../../pages/sync.page';
import { WorkViewPage } from '../../pages/work-view.page';
import { isWebDavServerUp } from '../../utils/check-webdav';
import {
WEBDAV_CONFIG_TEMPLATE,
createUniqueSyncFolder,
@ -17,14 +16,6 @@ test.describe('WebDAV Sync Error Handling', () => {
// Run sync tests serially to avoid WebDAV server contention
test.describe.configure({ mode: 'serial' });
test.beforeAll(async () => {
const isUp = await isWebDavServerUp(WEBDAV_CONFIG_TEMPLATE.baseUrl);
if (!isUp) {
console.warn('WebDAV server not reachable. Skipping WebDAV tests.');
test.skip(true, 'WebDAV server not reachable');
}
});
test('should handle server unavailable during sync', async ({
browser,
baseURL,

View file

@ -1,9 +1,8 @@
import { expect, test } from '../../fixtures/test.fixture';
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 { isWebDavServerUp } from '../../utils/check-webdav';
import {
createSyncFolder,
generateSyncFolderName,
@ -20,14 +19,6 @@ test.describe('WebDAV Sync Expansion', () => {
// Run sync tests serially to avoid WebDAV server contention
test.describe.configure({ mode: 'serial' });
test.beforeAll(async () => {
const isUp = await isWebDavServerUp(WEBDAV_CONFIG_TEMPLATE.baseUrl);
if (!isUp) {
console.warn('WebDAV server not reachable. Skipping WebDAV tests.');
test.skip(true, 'WebDAV server not reachable');
}
});
test('should sync projects', async ({ browser, baseURL, request }) => {
test.slow();
const SYNC_FOLDER_NAME = generateSyncFolderName('e2e-expansion-proj');

View file

@ -1,8 +1,7 @@
import { test, expect } from '../../fixtures/test.fixture';
import { test, expect } from '../../fixtures/webdav.fixture';
import { SyncPage } from '../../pages/sync.page';
import { WorkViewPage } from '../../pages/work-view.page';
import { waitForAppReady, waitForStatePersistence } from '../../utils/waits';
import { isWebDavServerUp } from '../../utils/check-webdav';
import {
WEBDAV_CONFIG_TEMPLATE,
setupSyncClient,
@ -24,14 +23,6 @@ test.describe('WebDAV Sync Full Flow', () => {
syncFolderPath: `/${SYNC_FOLDER_NAME}`,
};
test.beforeAll(async () => {
const isUp = await isWebDavServerUp(WEBDAV_CONFIG_TEMPLATE.baseUrl);
if (!isUp) {
console.warn('WebDAV server not reachable. Skipping WebDAV tests.');
test.skip(true, 'WebDAV server not reachable');
}
});
test('should sync data between two clients', async ({ browser, baseURL, request }) => {
test.slow(); // Sync tests might take longer
console.log('Using baseURL:', baseURL);

View file

@ -1,8 +1,7 @@
import { test, expect } from '../../fixtures/test.fixture';
import { test, expect } from '../../fixtures/webdav.fixture';
import { SyncPage } from '../../pages/sync.page';
import { WorkViewPage } from '../../pages/work-view.page';
import { TagPage } from '../../pages/tag.page';
import { isWebDavServerUp } from '../../utils/check-webdav';
import {
WEBDAV_CONFIG_TEMPLATE,
createUniqueSyncFolder,
@ -15,14 +14,6 @@ test.describe('WebDAV Sync Tags', () => {
// Run sync tests serially to avoid WebDAV server contention
test.describe.configure({ mode: 'serial' });
test.beforeAll(async () => {
const isUp = await isWebDavServerUp(WEBDAV_CONFIG_TEMPLATE.baseUrl);
if (!isUp) {
console.warn('WebDAV server not reachable. Skipping WebDAV tests.');
test.skip(true, 'WebDAV server not reachable');
}
});
test('should sync tag creation between clients', async ({
browser,
baseURL,

View file

@ -1,7 +1,6 @@
import { test, expect } from '../../fixtures/test.fixture';
import { test, expect } from '../../fixtures/webdav.fixture';
import { SyncPage } from '../../pages/sync.page';
import { WorkViewPage } from '../../pages/work-view.page';
import { isWebDavServerUp } from '../../utils/check-webdav';
import {
WEBDAV_CONFIG_TEMPLATE,
createUniqueSyncFolder,
@ -14,14 +13,6 @@ test.describe('WebDAV Sync Task Order', () => {
// Run sync tests serially to avoid WebDAV server contention
test.describe.configure({ mode: 'serial' });
test.beforeAll(async () => {
const isUp = await isWebDavServerUp(WEBDAV_CONFIG_TEMPLATE.baseUrl);
if (!isUp) {
console.warn('WebDAV server not reachable. Skipping WebDAV tests.');
test.skip(true, 'WebDAV server not reachable');
}
});
test('should preserve task order after sync', async ({ browser, baseURL, request }) => {
test.slow();
const SYNC_FOLDER_NAME = createUniqueSyncFolder('task-order');

View file

@ -1,10 +1,9 @@
import { expect } from '@playwright/test';
import { test } from '../../fixtures/test.fixture';
import { test } from '../../fixtures/webdav.fixture';
import { SyncPage } from '../../pages/sync.page';
import { WorkViewPage } from '../../pages/work-view.page';
import { TaskPage } from '../../pages/task.page';
import { waitForStatePersistence, waitForAppReady } from '../../utils/waits';
import { isWebDavServerUp } from '../../utils/check-webdav';
import {
WEBDAV_CONFIG_TEMPLATE,
setupSyncClient,
@ -28,14 +27,6 @@ import {
test.describe('@webdav WebDAV TODAY Tag Sync', () => {
test.describe.configure({ mode: 'serial' });
test.beforeAll(async () => {
const isUp = await isWebDavServerUp(WEBDAV_CONFIG_TEMPLATE.baseUrl);
if (!isUp) {
console.warn('WebDAV server not reachable. Skipping WebDAV TODAY tag tests.');
test.skip(true, 'WebDAV server not reachable');
}
});
/**
* Test 2.1: Concurrent task reordering in TODAY view
*