mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
fix(e2e): fix failing tags test and flaky late-join test
- Tags test: Use right-click context menu approach instead of 'g' shortcut to avoid typing into editable task title - Tags test: Add robust dismissAllOverlays helper with multiple Escape presses and backdrop click fallback - Late-join test: Improve conflict dialog handling with retry loop and wait for dialog to close - Late-join test: Add extra sync cycle after conflict resolution for more reliable data propagation - All files: Fix incorrect port in warning messages (1900 -> 1901) All 48 supersync e2e tests pass consistently.
This commit is contained in:
parent
380fc9127e
commit
0b998459aa
12 changed files with 93 additions and 95 deletions
|
|
@ -17,7 +17,7 @@ import { ImportPage } from '../../pages/import.page';
|
|||
* This includes active tasks, archived tasks (worklog), and projects.
|
||||
*
|
||||
* Prerequisites:
|
||||
* - super-sync-server running on localhost:1900 with TEST_MODE=true
|
||||
* - super-sync-server running on localhost:1901 with TEST_MODE=true
|
||||
* - Frontend running on localhost:4242
|
||||
*
|
||||
* Run with: npm run e2e:playwright -- --grep @importsync
|
||||
|
|
@ -35,7 +35,7 @@ base.describe('@importsync @supersync Import + Sync E2E', () => {
|
|||
serverHealthy = await isServerHealthy();
|
||||
if (!serverHealthy) {
|
||||
console.warn(
|
||||
'SuperSync server not healthy at http://localhost:1900 - skipping tests',
|
||||
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ base.describe('@supersync SuperSync Advanced Edge Cases', () => {
|
|||
serverHealthy = await isServerHealthy();
|
||||
if (!serverHealthy) {
|
||||
console.warn(
|
||||
'SuperSync server not healthy at http://localhost:1900 - skipping tests',
|
||||
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ base.describe('@supersync SuperSync Advanced', () => {
|
|||
serverHealthy = await isServerHealthy();
|
||||
if (!serverHealthy) {
|
||||
console.warn(
|
||||
'SuperSync server not healthy at http://localhost:1900 - skipping tests',
|
||||
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ base.describe('@supersync Daily Summary Sync', () => {
|
|||
serverHealthy = await isServerHealthy();
|
||||
if (!serverHealthy) {
|
||||
console.warn(
|
||||
'SuperSync server not healthy at http://localhost:1900 - skipping tests',
|
||||
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ base.describe('@supersync SuperSync Edge Cases', () => {
|
|||
serverHealthy = await isServerHealthy();
|
||||
if (!serverHealthy) {
|
||||
console.warn(
|
||||
'SuperSync server not healthy at http://localhost:1900 - skipping tests',
|
||||
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ base.describe('@supersync SuperSync Encryption', () => {
|
|||
serverHealthy = await isServerHealthy();
|
||||
if (!serverHealthy) {
|
||||
console.warn(
|
||||
'SuperSync server not healthy at http://localhost:1900 - skipping tests',
|
||||
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ base.describe('@supersync SuperSync Late Join', () => {
|
|||
serverHealthy = await isServerHealthy();
|
||||
if (!serverHealthy) {
|
||||
console.warn(
|
||||
'SuperSync server not healthy at http://localhost:1900 - skipping tests',
|
||||
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -92,37 +92,45 @@ base.describe('@supersync SuperSync Late Join', () => {
|
|||
// Since B has local data and server has remote data, and they might touch global settings or similar,
|
||||
// a conflict might occur. However, tasks are distinct IDs, so they should merge cleanly.
|
||||
// If a conflict dialog appears for Global Config or similar, we should handle it.
|
||||
// The helper `syncAndWait` doesn't handle conflicts automatically, but the tests in `supersync.spec.ts`
|
||||
// seem to expect `Conflict dialog detected` in logs and handle it?
|
||||
// Let's check if conflict dialog is visible and resolve it if so.
|
||||
// Usually "Use Remote" or "Use Local" is fine if keys don't overlap.
|
||||
// For distinct tasks, no conflict should occur on the tasks themselves.
|
||||
|
||||
// Check multiple times as dialog might appear with slight delay
|
||||
const conflictDialog = clientB.page.locator('dialog-conflict-resolution');
|
||||
if (await conflictDialog.isVisible({ timeout: 2000 })) {
|
||||
console.log('Conflict dialog detected on B, resolving...');
|
||||
// Click "Use Local" or "Use Remote" or "Merge"
|
||||
// Since we want to keep B's local tasks and A's remote tasks, valid merge should happen.
|
||||
// But if it's a conflict on a singleton model (like Global Config), we just pick one.
|
||||
// Let's pick Remote for config.
|
||||
const useRemoteBtn = conflictDialog
|
||||
.locator('button')
|
||||
.filter({ hasText: 'Remote' })
|
||||
.first();
|
||||
if (await useRemoteBtn.isVisible()) {
|
||||
await useRemoteBtn.click();
|
||||
} else {
|
||||
// Fallback
|
||||
await clientB.page.keyboard.press('Escape');
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
try {
|
||||
await conflictDialog.waitFor({ state: 'visible', timeout: 2000 });
|
||||
console.log('Conflict dialog detected on B, resolving...');
|
||||
// Pick Remote for singleton models (like Global Config)
|
||||
const useRemoteBtn = conflictDialog
|
||||
.locator('button')
|
||||
.filter({ hasText: 'Remote' })
|
||||
.first();
|
||||
if (await useRemoteBtn.isVisible()) {
|
||||
await useRemoteBtn.click();
|
||||
// Wait for dialog to close
|
||||
await conflictDialog.waitFor({ state: 'hidden', timeout: 5000 });
|
||||
} else {
|
||||
// Fallback: dismiss dialog
|
||||
await clientB.page.keyboard.press('Escape');
|
||||
}
|
||||
// Brief wait for any subsequent dialogs
|
||||
await clientB.page.waitForTimeout(500);
|
||||
} catch {
|
||||
// No conflict dialog visible, proceed
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for sync to settle
|
||||
await clientB.page.waitForTimeout(1000);
|
||||
// Wait for sync to fully settle after conflict resolution
|
||||
await clientB.page.waitForTimeout(2000);
|
||||
|
||||
// Trigger another sync to ensure all data is propagated
|
||||
await clientB.sync.syncAndWait();
|
||||
|
||||
// A Syncs to get B's data
|
||||
await clientA.sync.syncAndWait();
|
||||
|
||||
// Brief wait for state to propagate
|
||||
await clientA.page.waitForTimeout(1000);
|
||||
|
||||
// VERIFICATION
|
||||
// Both clients should have A1, A2, A3, B1, B2, B3
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ base.describe('@supersync SuperSync LWW Conflict Resolution', () => {
|
|||
serverHealthy = await isServerHealthy();
|
||||
if (!serverHealthy) {
|
||||
console.warn(
|
||||
'SuperSync server not healthy at http://localhost:1900 - skipping tests',
|
||||
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ base.describe('@supersync SuperSync Models', () => {
|
|||
serverHealthy = await isServerHealthy();
|
||||
if (!serverHealthy) {
|
||||
console.warn(
|
||||
'SuperSync server not healthy at http://localhost:1900 - skipping tests',
|
||||
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -259,62 +259,57 @@ base.describe('@supersync SuperSync Models', () => {
|
|||
// Create task
|
||||
await clientA.workView.addTask(taskName);
|
||||
|
||||
// Add tag to task via task detail
|
||||
// Helper to dismiss all overlays reliably
|
||||
const dismissAllOverlays = async (): Promise<void> => {
|
||||
// Dismiss backdrops by pressing Escape multiple times
|
||||
for (let j = 0; j < 3; j++) {
|
||||
await clientA.page.keyboard.press('Escape');
|
||||
await clientA.page.waitForTimeout(100);
|
||||
}
|
||||
// Wait for any overlays to disappear
|
||||
const overlayBackdrop = clientA.page.locator('.cdk-overlay-backdrop');
|
||||
try {
|
||||
await overlayBackdrop.waitFor({ state: 'hidden', timeout: 2000 });
|
||||
} catch {
|
||||
// If still visible, force click to dismiss
|
||||
if (await overlayBackdrop.isVisible()) {
|
||||
await overlayBackdrop.click({ force: true });
|
||||
await clientA.page.waitForTimeout(200);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Dismiss any existing overlays first
|
||||
await dismissAllOverlays();
|
||||
|
||||
// Add tag to task via context menu (most reliable method)
|
||||
const taskLocator = clientA.page.locator(`task:has-text("${taskName}")`);
|
||||
await taskLocator.click();
|
||||
await taskLocator.waitFor({ state: 'visible' });
|
||||
|
||||
// Hover to ensure buttons appear
|
||||
await taskLocator.hover();
|
||||
await clientA.page.waitForTimeout(500);
|
||||
|
||||
// Click "Edit Tags" button
|
||||
const editTagsBtn = clientA.page
|
||||
.locator('button[title="Edit tags"], button[aria-label="Edit tags"]')
|
||||
.first();
|
||||
|
||||
// Retry loop for opening tags menu
|
||||
// Retry loop for opening tags menu via context menu
|
||||
let menuOpened = false;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
try {
|
||||
if (await editTagsBtn.isVisible()) {
|
||||
await editTagsBtn.click();
|
||||
} else {
|
||||
// Fallback: Try context menu
|
||||
await taskLocator.click({ button: 'right' });
|
||||
// The context menu item is named "Toggle Tags" in English (from T.F.TASK.CMP.TOGGLE_TAGS)
|
||||
// But we also check for "Edit tags" just in case
|
||||
const ctxEditTags = clientA.page.locator('.e2e-edit-tags-btn').first();
|
||||
// Ensure clean state before each attempt
|
||||
await dismissAllOverlays();
|
||||
|
||||
if (await ctxEditTags.isVisible()) {
|
||||
await ctxEditTags.click();
|
||||
} else {
|
||||
// Last resort: shortcut
|
||||
console.log('Context menu item not found, trying shortcut...');
|
||||
// Close context menu if open
|
||||
const backdrop = clientA.page.locator('.cdk-overlay-backdrop');
|
||||
if (await backdrop.isVisible()) {
|
||||
await backdrop.click({ force: true });
|
||||
} else {
|
||||
await clientA.page.keyboard.press('Escape');
|
||||
}
|
||||
await clientA.page.waitForTimeout(200);
|
||||
// Right-click on task to open context menu (doesn't trigger edit mode)
|
||||
await taskLocator.click({ button: 'right' });
|
||||
await clientA.page.waitForTimeout(300);
|
||||
|
||||
// Ensure task is focused/selected
|
||||
await taskLocator.click({ force: true });
|
||||
await clientA.page.waitForTimeout(200);
|
||||
// Use keyboard shortcut to open the tag menu (default: "G")
|
||||
await clientA.page.keyboard.press('g');
|
||||
}
|
||||
}
|
||||
// Find and click the "Edit tags" button in context menu
|
||||
const ctxEditTags = clientA.page.locator('.e2e-edit-tags-btn').first();
|
||||
await ctxEditTags.waitFor({ state: 'visible', timeout: 3000 });
|
||||
await ctxEditTags.click();
|
||||
|
||||
// Dialog "Edit Tags" - now it's a menu
|
||||
const menuPanel = clientA.page.locator('.mat-mdc-menu-panel');
|
||||
await menuPanel.waitFor({ state: 'visible', timeout: 3000 });
|
||||
// Wait for tag submenu to appear
|
||||
const menuPanel = clientA.page.locator('.mat-mdc-menu-panel').last();
|
||||
await menuPanel.waitFor({ state: 'visible', timeout: 5000 });
|
||||
menuOpened = true;
|
||||
break; // Success
|
||||
break;
|
||||
} catch (e) {
|
||||
console.log(`Attempt ${i + 1} to open tags menu failed: ${e}, retrying...`);
|
||||
await clientA.page.keyboard.press('Escape'); // Reset state
|
||||
await dismissAllOverlays();
|
||||
await clientA.page.waitForTimeout(500);
|
||||
}
|
||||
}
|
||||
|
|
@ -323,23 +318,18 @@ base.describe('@supersync SuperSync Models', () => {
|
|||
throw new Error('Failed to open tags menu after 3 attempts');
|
||||
}
|
||||
|
||||
const menuPanel = clientA.page.locator('.mat-mdc-menu-panel');
|
||||
// Select the tag from list
|
||||
const tagOption = menuPanel
|
||||
// Select the tag from the submenu (last menu panel is the tag submenu)
|
||||
const tagMenu = clientA.page.locator('.mat-mdc-menu-panel').last();
|
||||
const tagOption = tagMenu
|
||||
.locator('button[mat-menu-item]')
|
||||
.filter({ hasText: tagName })
|
||||
.first();
|
||||
if (await tagOption.isVisible()) {
|
||||
await tagOption.click();
|
||||
} else {
|
||||
console.log('Tag not found in menu, creating new via prompt if needed');
|
||||
// If not found, maybe we need to scroll or it's not in the list
|
||||
}
|
||||
await tagOption.waitFor({ state: 'visible', timeout: 3000 });
|
||||
await tagOption.click();
|
||||
|
||||
// Close menu if still open (clicking option might close it)
|
||||
if (await menuPanel.isVisible()) {
|
||||
await clientA.page.keyboard.press('Escape');
|
||||
}
|
||||
// Wait for menus to close after selection
|
||||
await clientA.page.waitForTimeout(500);
|
||||
await dismissAllOverlays();
|
||||
|
||||
// Ensure tag is shown on Client A before syncing
|
||||
await expect(taskLocator).toContainText(tagName);
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ base.describe('@supersync SuperSync Repeatable Task Sync', () => {
|
|||
serverHealthy = await isServerHealthy();
|
||||
if (!serverHealthy) {
|
||||
console.warn(
|
||||
'SuperSync server not healthy at http://localhost:1900 - skipping tests',
|
||||
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ base.describe.serial('@supersync SuperSync Server Migration', () => {
|
|||
serverHealthy = await isServerHealthy();
|
||||
if (!serverHealthy) {
|
||||
console.warn(
|
||||
'SuperSync server not healthy at http://localhost:1900 - skipping tests',
|
||||
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { waitForAppReady } from '../../utils/waits';
|
|||
* the full stack including UI, network, and real IndexedDB isolation.
|
||||
*
|
||||
* Prerequisites:
|
||||
* - super-sync-server running on localhost:1900 with TEST_MODE=true
|
||||
* - super-sync-server running on localhost:1901 with TEST_MODE=true
|
||||
* - Frontend running on localhost:4242
|
||||
*
|
||||
* Run with: npm run e2e:supersync
|
||||
|
|
@ -41,7 +41,7 @@ base.describe('@supersync SuperSync E2E', () => {
|
|||
serverHealthy = await isServerHealthy();
|
||||
if (!serverHealthy) {
|
||||
console.warn(
|
||||
'SuperSync server not healthy at http://localhost:1900 - skipping tests',
|
||||
'SuperSync server not healthy at http://localhost:1901 - skipping tests',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue