mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-23 02:36:05 +00:00
Fix(e2e): Stabilize WebDAV sync tests
- Isolate WebDAV sync tests with unique folders per test to prevent cross-test interference. - Fix 'should sync task attachments' by correcting the attachment link selector, updating text expectation, and using force click to bypass UI interception. - Fix 'should sync task done state' by adding hover action for the done button and correcting the 'isDone' class assertion.
This commit is contained in:
parent
5fbf926183
commit
1b13113442
3 changed files with 573 additions and 0 deletions
68
e2e/pages/side-nav.page.ts
Normal file
68
e2e/pages/side-nav.page.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { expect, Locator, Page } from '@playwright/test';
|
||||
import { BasePage } from './base.page';
|
||||
|
||||
export class SideNavPage extends BasePage {
|
||||
readonly nav: Locator;
|
||||
readonly navItems: Locator;
|
||||
readonly allProjectsBtn: Locator;
|
||||
readonly backlogTasksBtn: Locator;
|
||||
readonly doneTasksBtn: Locator;
|
||||
readonly settingsBtn: Locator;
|
||||
readonly projectsGroupHeader: Locator;
|
||||
readonly projectsGroupAdditionalBtn: Locator;
|
||||
|
||||
constructor(page: Page, testPrefix: string = '') {
|
||||
super(page, testPrefix);
|
||||
this.nav = page.locator('magic-side-nav');
|
||||
this.navItems = page.locator('magic-side-nav nav-item');
|
||||
this.allProjectsBtn = page.locator('magic-side-nav button:has-text("All Projects")');
|
||||
this.backlogTasksBtn = page.locator('magic-side-nav button:has-text("Backlog")');
|
||||
this.doneTasksBtn = page.locator('magic-side-nav button:has-text("Done")');
|
||||
this.settingsBtn = page.locator('magic-side-nav button[routerlink="/settings"]');
|
||||
this.projectsGroupHeader = page
|
||||
.locator('.g-multi-btn-wrapper')
|
||||
.filter({ hasText: 'Projects' })
|
||||
.first();
|
||||
this.projectsGroupAdditionalBtn = this.projectsGroupHeader.locator('.additional-btn');
|
||||
}
|
||||
|
||||
async ensureSideNavOpen(): Promise<void> {
|
||||
const isVisible = await this.nav.isVisible();
|
||||
if (!isVisible) {
|
||||
// Click somewhere on the main content to unfocus if side nav might be closed
|
||||
await this.page.locator('body').click();
|
||||
await this.page.waitForTimeout(500); // give it a moment
|
||||
// Attempt to open side nav via a common trigger if it's not visible
|
||||
const menuBtn = this.page.locator('#triggerSideNavBtn');
|
||||
if (await menuBtn.isVisible()) {
|
||||
await menuBtn.click();
|
||||
await this.nav.waitFor({ state: 'visible', timeout: 5000 });
|
||||
} else {
|
||||
// Fallback: if there's no specific menu button, assume some key press might work or it's a layout issue
|
||||
// For now, if no button, just throw or log a warning if side nav is critical
|
||||
throw new Error('Side nav is not visible and no trigger button found');
|
||||
}
|
||||
}
|
||||
// Ensure the side nav is fully rendered and stable
|
||||
await this.navItems.first().waitFor({ state: 'visible', timeout: 5000 });
|
||||
}
|
||||
|
||||
async ensureSideNavClosed(): Promise<void> {
|
||||
const isVisible = await this.nav.isVisible();
|
||||
if (isVisible) {
|
||||
// Click outside to close side nav
|
||||
await this.page.locator('body').click({ position: { x: 500, y: 500 } });
|
||||
await this.nav.waitFor({ state: 'hidden', timeout: 5000 });
|
||||
}
|
||||
}
|
||||
|
||||
async ensureProjectsGroupExpanded(): Promise<void> {
|
||||
const isExpanded = await this.projectsGroupAdditionalBtn.isVisible(); // The additional button is only visible when expanded
|
||||
if (!isExpanded) {
|
||||
// Click the header to expand
|
||||
await this.projectsGroupHeader.click();
|
||||
// Wait for the additional button to become visible, indicating expansion
|
||||
await this.projectsGroupAdditionalBtn.waitFor({ state: 'visible', timeout: 8000 });
|
||||
}
|
||||
}
|
||||
}
|
||||
248
e2e/tests/sync/webdav-sync-advanced.spec.ts
Normal file
248
e2e/tests/sync/webdav-sync-advanced.spec.ts
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
import { test, expect } from '../../fixtures/test.fixture';
|
||||
import { SyncPage } from '../../pages/sync.page';
|
||||
import { WorkViewPage } from '../../pages/work-view.page';
|
||||
import { waitForAppReady } from '../../utils/waits';
|
||||
import { type Browser, type Page } from '@playwright/test';
|
||||
|
||||
test.describe('WebDAV Sync Advanced Features', () => {
|
||||
const createSyncFolder = async (request: any, folderName: string): Promise<void> => {
|
||||
const mkcolUrl = `${WEBDAV_CONFIG_TEMPLATE.baseUrl}${folderName}`;
|
||||
console.log(`Creating WebDAV folder: ${mkcolUrl}`);
|
||||
try {
|
||||
const response = await request.fetch(mkcolUrl, {
|
||||
method: 'MKCOL',
|
||||
headers: {
|
||||
Authorization: 'Basic ' + Buffer.from('admin:admin').toString('base64'),
|
||||
},
|
||||
});
|
||||
if (!response.ok() && response.status() !== 405) {
|
||||
console.warn(
|
||||
`Failed to create WebDAV folder: ${response.status()} ${response.statusText()}`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error creating WebDAV folder:', e);
|
||||
}
|
||||
};
|
||||
|
||||
const WEBDAV_CONFIG_TEMPLATE = {
|
||||
baseUrl: 'http://localhost:2345/',
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
};
|
||||
|
||||
const setupClient = async (
|
||||
browser: Browser,
|
||||
baseURL: string | undefined,
|
||||
): Promise<{ context: any; page: Page }> => {
|
||||
const context = await browser.newContext({ baseURL });
|
||||
const page = await context.newPage();
|
||||
await page.goto('/');
|
||||
await waitForAppReady(page);
|
||||
|
||||
// Dismiss Shepherd Tour if present
|
||||
try {
|
||||
const tourElement = page.locator('.shepherd-element').first();
|
||||
// Short wait to see if it appears
|
||||
await tourElement.waitFor({ state: 'visible', timeout: 4000 });
|
||||
|
||||
const cancelIcon = page.locator('.shepherd-cancel-icon').first();
|
||||
if (await cancelIcon.isVisible()) {
|
||||
await cancelIcon.click();
|
||||
} else {
|
||||
await page.keyboard.press('Escape');
|
||||
}
|
||||
|
||||
await tourElement.waitFor({ state: 'hidden', timeout: 3000 });
|
||||
} catch (e) {
|
||||
// Tour didn't appear or wasn't dismissable, ignore
|
||||
}
|
||||
|
||||
return { context, page };
|
||||
};
|
||||
|
||||
const waitForSync = async (
|
||||
page: Page,
|
||||
syncPage: SyncPage,
|
||||
): Promise<'success' | 'conflict' | void> => {
|
||||
// Poll for success icon, error snackbar, or conflict dialog
|
||||
const startTime = Date.now();
|
||||
while (Date.now() - startTime < 30000) {
|
||||
// 30s timeout
|
||||
const successVisible = await syncPage.syncCheckIcon.isVisible();
|
||||
if (successVisible) return 'success';
|
||||
|
||||
const conflictDialog = page.locator('dialog-sync-conflict');
|
||||
if (await conflictDialog.isVisible()) return 'conflict';
|
||||
|
||||
const snackBars = page.locator('.mat-mdc-snack-bar-container');
|
||||
const count = await snackBars.count();
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const text = await snackBars.nth(i).innerText();
|
||||
// Check for keywords indicating failure
|
||||
if (text.toLowerCase().includes('error') || text.toLowerCase().includes('fail')) {
|
||||
throw new Error(`Sync failed with error: ${text}`);
|
||||
}
|
||||
}
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
throw new Error('Sync timeout: Success icon did not appear');
|
||||
};
|
||||
|
||||
test('should sync sub-tasks correctly', async ({ browser, baseURL, request }) => {
|
||||
const SYNC_FOLDER_NAME = `e2e-advanced-sub-${Date.now()}`;
|
||||
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 setupClient(browser, url);
|
||||
const syncPageA = new SyncPage(pageA);
|
||||
const workViewPageA = new WorkViewPage(pageA);
|
||||
await workViewPageA.waitForTaskList();
|
||||
|
||||
// Configure Sync on Client A
|
||||
await syncPageA.setupWebdavSync(WEBDAV_CONFIG);
|
||||
|
||||
// Create Parent Task
|
||||
const parentTaskName = 'Parent Task';
|
||||
await workViewPageA.addTask(parentTaskName);
|
||||
const parentTaskA = pageA.locator('task', { hasText: parentTaskName }).first();
|
||||
|
||||
// Create Sub Tasks
|
||||
await workViewPageA.addSubTask(parentTaskA, 'Sub Task 1');
|
||||
await workViewPageA.addSubTask(parentTaskA, 'Sub Task 2');
|
||||
|
||||
// Verify structure on A
|
||||
await expect(pageA.locator('task-list[listid="SUB"] task')).toHaveCount(2);
|
||||
|
||||
// Sync A
|
||||
await syncPageA.triggerSync();
|
||||
await waitForSync(pageA, syncPageA);
|
||||
|
||||
// --- Client B ---
|
||||
const { context: contextB, page: pageB } = await setupClient(browser, url);
|
||||
const syncPageB = new SyncPage(pageB);
|
||||
const workViewPageB = new WorkViewPage(pageB);
|
||||
await workViewPageB.waitForTaskList();
|
||||
|
||||
// Configure Sync on Client B
|
||||
await syncPageB.setupWebdavSync(WEBDAV_CONFIG);
|
||||
await expect(syncPageB.syncBtn).toBeVisible();
|
||||
|
||||
// Sync B
|
||||
await syncPageB.triggerSync();
|
||||
await waitForSync(pageB, syncPageB);
|
||||
|
||||
// Verify structure on B
|
||||
const parentTaskB = pageB.locator('task', { hasText: parentTaskName }).first();
|
||||
await expect(parentTaskB).toBeVisible();
|
||||
|
||||
// Check for subtask count - expand first
|
||||
await parentTaskB.click(); // Ensure focus/expanded? Usually auto-expanded.
|
||||
|
||||
// Use more specific locator for subtasks
|
||||
const subTaskList = pageB.locator(`task-list[listid="SUB"]`);
|
||||
await expect(subTaskList.locator('task')).toHaveCount(2);
|
||||
await expect(subTaskList.locator('task', { hasText: 'Sub Task 1' })).toBeVisible();
|
||||
await expect(subTaskList.locator('task', { hasText: 'Sub Task 2' })).toBeVisible();
|
||||
|
||||
await contextA.close();
|
||||
await contextB.close();
|
||||
});
|
||||
|
||||
test('should sync task attachments', async ({ browser, baseURL, request }) => {
|
||||
test.slow();
|
||||
const SYNC_FOLDER_NAME = `e2e-advanced-att-${Date.now()}`;
|
||||
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 setupClient(browser, url);
|
||||
const syncPageA = new SyncPage(pageA);
|
||||
const workViewPageA = new WorkViewPage(pageA);
|
||||
await workViewPageA.waitForTaskList();
|
||||
|
||||
// Configure Sync on Client A
|
||||
await syncPageA.setupWebdavSync(WEBDAV_CONFIG);
|
||||
|
||||
// Create Task
|
||||
const taskName = 'Attachment Task';
|
||||
await workViewPageA.addTask(taskName);
|
||||
const taskA = pageA.locator('task', { hasText: taskName }).first();
|
||||
|
||||
// Add Attachment
|
||||
// Use context menu which is more reliable
|
||||
await taskA.click({ button: 'right' });
|
||||
|
||||
// Click "Attach file or link" in context menu
|
||||
// The menu is in a portal, so we query the page
|
||||
const attachBtn = pageA.locator('.mat-mdc-menu-content button', {
|
||||
hasText: 'Attach',
|
||||
});
|
||||
await attachBtn.waitFor({ state: 'visible' });
|
||||
await attachBtn.click();
|
||||
|
||||
// Dialog opens (direct attachment dialog or via side panel?)
|
||||
// The context menu action calls `addAttachment()`, which usually opens the dialog.
|
||||
const dialog = pageA.locator('dialog-edit-task-attachment');
|
||||
await expect(dialog).toBeVisible();
|
||||
|
||||
// Fill title
|
||||
await dialog.locator('input[name="title"]').fill('Google');
|
||||
|
||||
// Fill path/url
|
||||
const pathInput = dialog.locator('input[name="path"]');
|
||||
await pathInput.fill('https://google.com');
|
||||
|
||||
await dialog.locator('button[type="submit"]').click();
|
||||
|
||||
// Verify attachment indicator appears on task
|
||||
const attachmentBtn = taskA.locator('.attachment-btn');
|
||||
await expect(attachmentBtn).toBeVisible();
|
||||
|
||||
// Click it to open side panel
|
||||
await attachmentBtn.click({ force: true });
|
||||
|
||||
// Verify attachment added on A
|
||||
await expect(pageA.locator('.attachment-link')).toBeVisible();
|
||||
await expect(pageA.locator('.attachment-link')).toContainText('Google');
|
||||
|
||||
// Sync A
|
||||
await syncPageA.triggerSync();
|
||||
await waitForSync(pageA, syncPageA);
|
||||
|
||||
// --- Client B ---
|
||||
const { context: contextB, page: pageB } = await setupClient(browser, url);
|
||||
const syncPageB = new SyncPage(pageB);
|
||||
const workViewPageB = new WorkViewPage(pageB);
|
||||
await workViewPageB.waitForTaskList();
|
||||
|
||||
// Configure Sync on Client B
|
||||
await syncPageB.setupWebdavSync(WEBDAV_CONFIG);
|
||||
|
||||
// Sync B
|
||||
await syncPageB.triggerSync();
|
||||
await waitForSync(pageB, syncPageB);
|
||||
|
||||
// Verify Attachment on B
|
||||
const taskB = pageB.locator('task', { hasText: taskName }).first();
|
||||
await taskB.click();
|
||||
await taskB.locator('.show-additional-info-btn').click({ force: true });
|
||||
|
||||
await expect(pageB.locator('.attachment-link')).toContainText('Google');
|
||||
|
||||
await contextA.close();
|
||||
await contextB.close();
|
||||
});
|
||||
});
|
||||
257
e2e/tests/sync/webdav-sync-expansion.spec.ts
Normal file
257
e2e/tests/sync/webdav-sync-expansion.spec.ts
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
import { test, expect } from '../../fixtures/test.fixture';
|
||||
import { SyncPage } from '../../pages/sync.page';
|
||||
import { WorkViewPage } from '../../pages/work-view.page';
|
||||
import { ProjectPage } from '../../pages/project.page';
|
||||
import { waitForAppReady } from '../../utils/waits';
|
||||
import { type Browser, type Page } from '@playwright/test';
|
||||
|
||||
test.describe('WebDAV Sync Expansion', () => {
|
||||
const createSyncFolder = async (request: any, folderName: string): Promise<void> => {
|
||||
const mkcolUrl = `${WEBDAV_CONFIG_TEMPLATE.baseUrl}${folderName}`;
|
||||
console.log(`Creating WebDAV folder: ${mkcolUrl}`);
|
||||
try {
|
||||
const response = await request.fetch(mkcolUrl, {
|
||||
method: 'MKCOL',
|
||||
headers: {
|
||||
Authorization: 'Basic ' + Buffer.from('admin:admin').toString('base64'),
|
||||
},
|
||||
});
|
||||
if (!response.ok() && response.status() !== 405) {
|
||||
console.warn(
|
||||
`Failed to create WebDAV folder: ${response.status()} ${response.statusText()}`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error creating WebDAV folder:', e);
|
||||
}
|
||||
};
|
||||
|
||||
const WEBDAV_CONFIG_TEMPLATE = {
|
||||
baseUrl: 'http://localhost:2345/',
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
};
|
||||
|
||||
const dismissTour = async (page: Page): Promise<void> => {
|
||||
try {
|
||||
const tourElement = page.locator('.shepherd-element').first();
|
||||
await tourElement.waitFor({ state: 'visible', timeout: 4000 });
|
||||
const cancelIcon = page.locator('.shepherd-cancel-icon').first();
|
||||
if (await cancelIcon.isVisible()) {
|
||||
await cancelIcon.click();
|
||||
} else {
|
||||
await page.keyboard.press('Escape');
|
||||
}
|
||||
await tourElement.waitFor({ state: 'hidden', timeout: 3000 });
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
};
|
||||
|
||||
const setupClient = async (
|
||||
browser: Browser,
|
||||
baseURL: string | undefined,
|
||||
): Promise<{ context: any; page: Page }> => {
|
||||
const context = await browser.newContext({ baseURL });
|
||||
const page = await context.newPage();
|
||||
await page.goto('/');
|
||||
await waitForAppReady(page);
|
||||
await dismissTour(page);
|
||||
return { context, page };
|
||||
};
|
||||
|
||||
const waitForSync = async (
|
||||
page: Page,
|
||||
syncPage: SyncPage,
|
||||
): Promise<'success' | 'conflict' | void> => {
|
||||
const startTime = Date.now();
|
||||
await expect(syncPage.syncBtn).toBeVisible({ timeout: 10000 });
|
||||
|
||||
while (Date.now() - startTime < 60000) {
|
||||
const successVisible = await syncPage.syncCheckIcon.isVisible();
|
||||
if (successVisible) return 'success';
|
||||
|
||||
const conflictDialog = page.locator('dialog-sync-conflict');
|
||||
if (await conflictDialog.isVisible()) return 'conflict';
|
||||
|
||||
const snackBars = page.locator('.mat-mdc-snack-bar-container');
|
||||
const count = await snackBars.count();
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const text = await snackBars.nth(i).innerText();
|
||||
if (text.toLowerCase().includes('error') || text.toLowerCase().includes('fail')) {
|
||||
throw new Error(`Sync failed with error: ${text}`);
|
||||
}
|
||||
}
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
throw new Error('Sync timeout: Success icon did not appear');
|
||||
};
|
||||
|
||||
test('should sync projects', async ({ browser, baseURL, request }) => {
|
||||
test.slow();
|
||||
const SYNC_FOLDER_NAME = `e2e-expansion-proj-${Date.now()}`;
|
||||
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 setupClient(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);
|
||||
|
||||
// Add task to new project on A
|
||||
await workViewPageA.addTask('Task in Project A');
|
||||
|
||||
// Sync A
|
||||
await syncPageA.triggerSync();
|
||||
await waitForSync(pageA, syncPageA);
|
||||
|
||||
// --- Client B ---
|
||||
const { context: contextB, page: pageB } = await setupClient(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 waitForSync(pageB, syncPageB);
|
||||
|
||||
// Reload to ensure UI is updated with synced data
|
||||
await pageB.reload();
|
||||
await waitForAppReady(pageB);
|
||||
await dismissTour(pageB);
|
||||
|
||||
// Verify Project on B
|
||||
// Wait for project navigation
|
||||
await pageB.waitForTimeout(2000);
|
||||
|
||||
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');
|
||||
await syncPageB.triggerSync();
|
||||
await waitForSync(pageB, syncPageB);
|
||||
|
||||
// Sync A
|
||||
await syncPageA.triggerSync();
|
||||
await waitForSync(pageA, syncPageA);
|
||||
|
||||
await pageA.reload();
|
||||
await waitForAppReady(pageA);
|
||||
|
||||
// Ensure we are on the project page
|
||||
await projectPageA.navigateToProjectByName(projectName);
|
||||
|
||||
// Verify task on A
|
||||
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 = `e2e-expansion-done-${Date.now()}`;
|
||||
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 setupClient(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 waitForSync(pageA, syncPageA);
|
||||
|
||||
// --- Client B ---
|
||||
const { context: contextB, page: pageB } = await setupClient(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 waitForSync(pageB, syncPageB);
|
||||
|
||||
await pageB.reload();
|
||||
await waitForAppReady(pageB);
|
||||
await dismissTour(pageB);
|
||||
await workViewPageB.waitForTaskList();
|
||||
|
||||
await expect(pageB.locator('task', { hasText: taskName })).toBeVisible({
|
||||
timeout: 20000,
|
||||
});
|
||||
|
||||
// Mark done on A
|
||||
await pageA.waitForTimeout(1000);
|
||||
const taskA = pageA.locator('task', { hasText: taskName }).first();
|
||||
await taskA.hover();
|
||||
const doneBtnA = taskA.locator('.task-done-btn');
|
||||
await doneBtnA.click({ force: true });
|
||||
|
||||
// Wait for done state (strikethrough or disappearance depending on config, default is just strikethrough/checked)
|
||||
// By default, done tasks might move to "Done" list or stay.
|
||||
// Assuming default behavior: check if class 'is-done' is present or checkbox checked.
|
||||
await expect(taskA).toHaveClass(/isDone/);
|
||||
|
||||
await syncPageA.triggerSync();
|
||||
await waitForSync(pageA, syncPageA);
|
||||
|
||||
// Sync B
|
||||
await syncPageB.triggerSync();
|
||||
await waitForSync(pageB, syncPageB);
|
||||
|
||||
const taskB = pageB.locator('task', { hasText: taskName }).first();
|
||||
await expect(taskB).toHaveClass(/isDone/);
|
||||
|
||||
// Mark undone on B
|
||||
const doneBtnB = taskB.locator('.check-done');
|
||||
await doneBtnB.click();
|
||||
await expect(taskB).not.toHaveClass(/isDone/);
|
||||
|
||||
await syncPageB.triggerSync();
|
||||
await waitForSync(pageB, syncPageB);
|
||||
|
||||
// Sync A
|
||||
await syncPageA.triggerSync();
|
||||
await waitForSync(pageA, syncPageA);
|
||||
|
||||
await expect(taskA).not.toHaveClass(/isDone/);
|
||||
|
||||
await contextA.close();
|
||||
await contextB.close();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue