mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-19 09:25:02 +00:00
Remove the full welcome tour (Welcome, AddTask, DeleteTask, Projects, Sync, IssueProviders, ProductivityHelper, FinalCongrats, StartTourAgain) and keep only the KeyboardNav tour, triggered from the Help menu. - Delete ShepherdComponent (auto-start on load) - Strip shepherd-steps.const.ts to KeyboardNav steps only - Simplify ShepherdService with _initPromise guard and fresh tour on re-open - Remove 3 tour menu items from Help menu, keep keyboard tour - Remove waitForEl/waitForElObs$ helpers (unused by KeyboardNav) - Delete e2e/utils/tour-helpers.ts and all tour dismissal calls - Clean up stale tour comments across e2e tests - Fix pre-existing build error: add isFinishDayEnabled to AppFeaturesConfig type, defaults, form toggle, component signal, and translations
174 lines
6.4 KiB
TypeScript
174 lines
6.4 KiB
TypeScript
import { test, expect } from '../../fixtures/supersync.fixture';
|
|
import {
|
|
createTestUser,
|
|
getSuperSyncConfig,
|
|
createSimulatedClient,
|
|
closeClient,
|
|
waitForTask,
|
|
type SimulatedE2EClient,
|
|
} from '../../utils/supersync-helpers';
|
|
import { SuperSyncPage } from '../../pages/supersync.page';
|
|
import { WorkViewPage } from '../../pages/work-view.page';
|
|
import { waitForAppReady } from '../../utils/waits';
|
|
|
|
/**
|
|
* SuperSync Multi-Tab Same Account E2E Tests
|
|
*
|
|
* Tests that two browser tabs sharing the same IndexedDB (same BrowserContext)
|
|
* don't corrupt data when using SuperSync.
|
|
*
|
|
* NOTE: The app prevents two tabs from running simultaneously via BroadcastChannel.
|
|
* This test works around that by navigating the inactive tab to about:blank before
|
|
* opening the next tab, then reloading it later. This simulates the real-world
|
|
* scenario of closing and reopening a tab.
|
|
*
|
|
* Prerequisites:
|
|
* - super-sync-server running on localhost:1901 with TEST_MODE=true
|
|
* - Frontend running on localhost:4242
|
|
*
|
|
* Run with: npm run e2e:supersync:file e2e/tests/sync/supersync-multi-tab.spec.ts
|
|
*/
|
|
|
|
test.describe('@supersync Multi-Tab Same Account', () => {
|
|
/**
|
|
* Scenario: Two tabs share IndexedDB, tasks created in one tab appear in other after reload
|
|
*
|
|
* Flow:
|
|
* 1. Create a SHARED browser context (same IndexedDB)
|
|
* 2. Open Tab 1, set up SuperSync, create task
|
|
* 3. Tab 1 syncs
|
|
* 4. Navigate Tab 1 away (avoid multi-instance blocker)
|
|
* 5. Open Tab 2 → should see Tab 1's task (from shared IndexedDB)
|
|
* 6. Tab 2 creates a task, syncs
|
|
* 7. Navigate Tab 2 away, reload Tab 1 → should see both tasks
|
|
* 8. External Client C (separate context) syncs → should see consistent state
|
|
*/
|
|
test('Two tabs sharing IndexedDB converge with external client', async ({
|
|
browser,
|
|
baseURL,
|
|
testRunId,
|
|
}) => {
|
|
test.setTimeout(180000);
|
|
const appUrl = baseURL || 'http://localhost:4242';
|
|
let clientC: SimulatedE2EClient | null = null;
|
|
let sharedContext: import('@playwright/test').BrowserContext | null = null;
|
|
|
|
try {
|
|
const user = await createTestUser(testRunId);
|
|
const syncConfig = getSuperSyncConfig(user);
|
|
|
|
// ============ PHASE 1: Setup shared context with Tab 1 ============
|
|
console.log('[MultiTab] Phase 1: Setup shared context with Tab 1');
|
|
|
|
sharedContext = await browser.newContext({
|
|
storageState: undefined,
|
|
viewport: { width: 1920, height: 1080 },
|
|
// Set PLAYWRIGHT user agent for test identification and to skip onboarding
|
|
userAgent: 'PLAYWRIGHT MULTI-TAB-TEST',
|
|
});
|
|
|
|
const tab1 = await sharedContext.newPage();
|
|
tab1.on('console', (msg) => {
|
|
if (msg.type() === 'error') {
|
|
console.error(`[Tab1] Console error:`, msg.text());
|
|
}
|
|
});
|
|
|
|
await tab1.goto(appUrl);
|
|
await waitForAppReady(tab1);
|
|
|
|
const tab1Sync = new SuperSyncPage(tab1);
|
|
const tab1WorkView = new WorkViewPage(tab1, `Tab1-${testRunId}`);
|
|
|
|
await tab1Sync.setupSuperSync(syncConfig);
|
|
console.log('[MultiTab] Tab 1 setup complete');
|
|
|
|
// ============ PHASE 2: Tab 1 creates task and syncs ============
|
|
console.log('[MultiTab] Phase 2: Tab 1 creates task');
|
|
|
|
const taskFromTab1 = `Tab1-Task-${testRunId}`;
|
|
await tab1WorkView.addTask(taskFromTab1);
|
|
await waitForTask(tab1, taskFromTab1);
|
|
|
|
await tab1Sync.syncAndWait();
|
|
console.log('[MultiTab] Tab 1 created task and synced');
|
|
|
|
// ============ PHASE 3: Open Tab 2 in same context ============
|
|
console.log('[MultiTab] Phase 3: Open Tab 2 in same context');
|
|
|
|
// Navigate Tab 1 away to avoid multi-instance blocker
|
|
// (app uses BroadcastChannel to prevent two tabs from running simultaneously)
|
|
await tab1.goto('about:blank');
|
|
|
|
const tab2 = await sharedContext.newPage();
|
|
tab2.on('console', (msg) => {
|
|
if (msg.type() === 'error') {
|
|
console.error(`[Tab2] Console error:`, msg.text());
|
|
}
|
|
});
|
|
|
|
await tab2.goto(appUrl);
|
|
await waitForAppReady(tab2);
|
|
await tab2.waitForTimeout(2000); // Let Angular hydrate from IndexedDB
|
|
|
|
// Tab 2 should see Tab 1's task from shared IndexedDB
|
|
await waitForTask(tab2, taskFromTab1);
|
|
console.log("[MultiTab] ✓ Tab 2 sees Tab 1's task from shared IndexedDB");
|
|
|
|
// ============ PHASE 4: Tab 2 creates task ============
|
|
console.log('[MultiTab] Phase 4: Tab 2 creates task');
|
|
|
|
const tab2WorkView = new WorkViewPage(tab2, `Tab2-${testRunId}`);
|
|
const taskFromTab2 = `Tab2-Task-${testRunId}`;
|
|
await tab2WorkView.addTask(taskFromTab2);
|
|
await waitForTask(tab2, taskFromTab2);
|
|
console.log('[MultiTab] Tab 2 created task');
|
|
|
|
// Trigger sync from Tab 2 (SuperSync config is shared via IndexedDB)
|
|
const tab2Sync = new SuperSyncPage(tab2);
|
|
await tab2Sync.syncAndWait();
|
|
console.log('[MultiTab] Tab 2 synced');
|
|
|
|
// ============ PHASE 5: Tab 1 reloads and verifies ============
|
|
console.log('[MultiTab] Phase 5: Tab 1 reloads');
|
|
|
|
// Navigate Tab 2 away before reloading Tab 1
|
|
await tab2.goto('about:blank');
|
|
|
|
await tab1.goto(appUrl);
|
|
await waitForAppReady(tab1);
|
|
await tab1.waitForTimeout(2000);
|
|
|
|
// Tab 1 should see both tasks
|
|
await waitForTask(tab1, taskFromTab1);
|
|
await waitForTask(tab1, taskFromTab2);
|
|
console.log('[MultiTab] ✓ Tab 1 sees both tasks after reload');
|
|
|
|
// ============ PHASE 6: External Client C verifies consistent state ============
|
|
console.log('[MultiTab] Phase 6: External Client C verifies');
|
|
|
|
// Navigate Tab 1 away before creating external client
|
|
await tab1.goto('about:blank');
|
|
|
|
clientC = await createSimulatedClient(browser, appUrl, 'C', testRunId);
|
|
await clientC.sync.setupSuperSync(syncConfig);
|
|
await clientC.sync.syncAndWait();
|
|
|
|
await waitForTask(clientC.page, taskFromTab1);
|
|
await waitForTask(clientC.page, taskFromTab2);
|
|
|
|
const cTask1 = clientC.page.locator(`task:has-text("${taskFromTab1}")`);
|
|
const cTask2 = clientC.page.locator(`task:has-text("${taskFromTab2}")`);
|
|
await expect(cTask1).toBeVisible();
|
|
await expect(cTask2).toBeVisible();
|
|
console.log('[MultiTab] ✓ External Client C sees consistent state');
|
|
|
|
console.log('[MultiTab] ✓ Multi-tab test PASSED!');
|
|
} finally {
|
|
if (clientC) await closeClient(clientC);
|
|
if (sharedContext) {
|
|
await sharedContext.close().catch(() => {});
|
|
}
|
|
}
|
|
});
|
|
});
|