diff --git a/e2e/fixtures/webdav.fixture.ts b/e2e/fixtures/webdav.fixture.ts new file mode 100644 index 000000000..b5c97e0d0 --- /dev/null +++ b/e2e/fixtures/webdav.fixture.ts @@ -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({ + /** + * 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'; diff --git a/e2e/tests/sync/webdav-first-sync-conflict.spec.ts b/e2e/tests/sync/webdav-first-sync-conflict.spec.ts index b982e8698..f1a0dc97f 100644 --- a/e2e/tests/sync/webdav-first-sync-conflict.spec.ts +++ b/e2e/tests/sync/webdav-first-sync-conflict.spec.ts @@ -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, diff --git a/e2e/tests/sync/webdav-legacy-migration-sync.spec.ts b/e2e/tests/sync/webdav-legacy-migration-sync.spec.ts index b9890a663..f491b42c6 100644 --- a/e2e/tests/sync/webdav-legacy-migration-sync.spec.ts +++ b/e2e/tests/sync/webdav-legacy-migration-sync.spec.ts @@ -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 * diff --git a/e2e/tests/sync/webdav-provider-switch.spec.ts b/e2e/tests/sync/webdav-provider-switch.spec.ts index 127a7b554..b0a2141de 100644 --- a/e2e/tests/sync/webdav-provider-switch.spec.ts +++ b/e2e/tests/sync/webdav-provider-switch.spec.ts @@ -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, diff --git a/e2e/tests/sync/webdav-sync-advanced.spec.ts b/e2e/tests/sync/webdav-sync-advanced.spec.ts index 68398e781..e73010cf0 100644 --- a/e2e/tests/sync/webdav-sync-advanced.spec.ts +++ b/e2e/tests/sync/webdav-sync-advanced.spec.ts @@ -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); diff --git a/e2e/tests/sync/webdav-sync-archive.spec.ts b/e2e/tests/sync/webdav-sync-archive.spec.ts index 2632a86f8..85c98c65a 100644 --- a/e2e/tests/sync/webdav-sync-archive.spec.ts +++ b/e2e/tests/sync/webdav-sync-archive.spec.ts @@ -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 * diff --git a/e2e/tests/sync/webdav-sync-delete-cascade.spec.ts b/e2e/tests/sync/webdav-sync-delete-cascade.spec.ts index 550fa3854..8eeaabd0e 100644 --- a/e2e/tests/sync/webdav-sync-delete-cascade.spec.ts +++ b/e2e/tests/sync/webdav-sync-delete-cascade.spec.ts @@ -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 => { 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 * diff --git a/e2e/tests/sync/webdav-sync-error-handling.spec.ts b/e2e/tests/sync/webdav-sync-error-handling.spec.ts index 2c5a9d290..34937b772 100644 --- a/e2e/tests/sync/webdav-sync-error-handling.spec.ts +++ b/e2e/tests/sync/webdav-sync-error-handling.spec.ts @@ -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, diff --git a/e2e/tests/sync/webdav-sync-expansion.spec.ts b/e2e/tests/sync/webdav-sync-expansion.spec.ts index bfad8aea5..695323f4d 100644 --- a/e2e/tests/sync/webdav-sync-expansion.spec.ts +++ b/e2e/tests/sync/webdav-sync-expansion.spec.ts @@ -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'); diff --git a/e2e/tests/sync/webdav-sync-full.spec.ts b/e2e/tests/sync/webdav-sync-full.spec.ts index c727a2300..dd925f8dd 100644 --- a/e2e/tests/sync/webdav-sync-full.spec.ts +++ b/e2e/tests/sync/webdav-sync-full.spec.ts @@ -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); diff --git a/e2e/tests/sync/webdav-sync-tags.spec.ts b/e2e/tests/sync/webdav-sync-tags.spec.ts index b547fe616..7d3c75c99 100644 --- a/e2e/tests/sync/webdav-sync-tags.spec.ts +++ b/e2e/tests/sync/webdav-sync-tags.spec.ts @@ -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, diff --git a/e2e/tests/sync/webdav-sync-task-order.spec.ts b/e2e/tests/sync/webdav-sync-task-order.spec.ts index 3ce4f4799..3ac958dad 100644 --- a/e2e/tests/sync/webdav-sync-task-order.spec.ts +++ b/e2e/tests/sync/webdav-sync-task-order.spec.ts @@ -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'); diff --git a/e2e/tests/sync/webdav-sync-today-tag.spec.ts b/e2e/tests/sync/webdav-sync-today-tag.spec.ts index 0ee5b8793..3735a6e0c 100644 --- a/e2e/tests/sync/webdav-sync-today-tag.spec.ts +++ b/e2e/tests/sync/webdav-sync-today-tag.spec.ts @@ -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 *