diff --git a/e2e/pages/sync.page.ts b/e2e/pages/sync.page.ts index ed9514784..f6101d2f0 100644 --- a/e2e/pages/sync.page.ts +++ b/e2e/pages/sync.page.ts @@ -31,131 +31,166 @@ export class SyncPage extends BasePage { password: string; syncFolderPath: string; }): Promise { - // Dismiss any visible snackbars/toasts that might block clicks - const snackBar = this.page.locator('.mat-mdc-snack-bar-container'); - if (await snackBar.isVisible({ timeout: 500 }).catch(() => false)) { - const dismissBtn = snackBar.locator('button'); - if (await dismissBtn.isVisible({ timeout: 500 }).catch(() => false)) { - await dismissBtn.click().catch(() => {}); + // Try entire setup flow up to 2 times (dialog-level retry) + for (let dialogAttempt = 0; dialogAttempt < 2; dialogAttempt++) { + if (dialogAttempt > 0) { + console.log(`[setupWebdavSync] Dialog-level retry attempt ${dialogAttempt + 1}`); } - await this.page.waitForTimeout(500); - } - // Ensure sync button is visible and clickable - await this.syncBtn.waitFor({ state: 'visible', timeout: 10000 }); - - // Click sync button to open settings dialog - use force click if needed - await this.syncBtn.click({ timeout: 5000 }); - - // Wait for dialog to appear - const dialog = this.page.locator('mat-dialog-container, .mat-mdc-dialog-container'); - const dialogVisible = await dialog - .waitFor({ state: 'visible', timeout: 5000 }) - .then(() => true) - .catch(() => false); - - // If dialog didn't open, try clicking again - if (!dialogVisible) { - await this.page.waitForTimeout(500); - await this.syncBtn.click({ force: true }); - await dialog.waitFor({ state: 'visible', timeout: 5000 }); - } - - // Wait for dialog to be fully loaded - await this.page.waitForLoadState('networkidle'); - await this.providerSelect.waitFor({ state: 'visible', timeout: 10000 }); - - // Wait a moment for Angular animations - await this.page.waitForTimeout(500); - - // Click on provider select to open dropdown with retry - const webdavOption = this.page.locator('mat-option').filter({ hasText: 'WebDAV' }); - - // Use the existing providerSelect locator which targets the mat-select directly - // Use the providerSelect locator that was already validated above - const selectElement = this.providerSelect; - - for (let attempt = 0; attempt < 5; attempt++) { - // Ensure the select is in view - await selectElement.scrollIntoViewIfNeeded({ timeout: 5000 }).catch(async () => { - // If scrollIntoViewIfNeeded fails, try scrolling the dialog content - const dialogContent = this.page.locator('mat-dialog-content'); - if (await dialogContent.isVisible()) { - await dialogContent.evaluate((el) => el.scrollTo(0, 0)); + // Dismiss any visible snackbars/toasts that might block clicks + const snackBar = this.page.locator('.mat-mdc-snack-bar-container'); + if (await snackBar.isVisible({ timeout: 500 }).catch(() => false)) { + const dismissBtn = snackBar.locator('button'); + if (await dismissBtn.isVisible({ timeout: 500 }).catch(() => false)) { + await dismissBtn.click().catch(() => {}); } - }); - await this.page.waitForTimeout(300); - - // Focus and click the select element - await selectElement.focus().catch(() => {}); - await this.page.waitForTimeout(200); - - // Try multiple ways to open the dropdown - if (attempt === 0) { - // First attempt: regular click - await selectElement.click().catch(() => {}); - } else if (attempt === 1) { - // Second attempt: use Space key to open - await this.page.keyboard.press('Space'); - } else if (attempt === 2) { - // Third attempt: use ArrowDown to open - await this.page.keyboard.press('ArrowDown'); - } else { - // Later attempts: force click - await selectElement.click({ force: true }).catch(() => {}); + await this.page.waitForTimeout(500); } - await this.page.waitForTimeout(500); - // Wait for any mat-option to appear (dropdown opened) - const anyOption = this.page.locator('mat-option').first(); - const anyOptionVisible = await anyOption - .waitFor({ state: 'visible', timeout: 3000 }) + // Ensure sync button is visible and clickable + await this.syncBtn.waitFor({ state: 'visible', timeout: 10000 }); + + // Click sync button to open settings dialog - use force click if needed + await this.syncBtn.click({ timeout: 5000 }); + + // Wait for dialog to appear + const dialog = this.page.locator('mat-dialog-container, .mat-mdc-dialog-container'); + const dialogVisible = await dialog + .waitFor({ state: 'visible', timeout: 5000 }) .then(() => true) .catch(() => false); - if (anyOptionVisible) { - // Now wait for WebDAV option specifically - const webdavVisible = await webdavOption + // If dialog didn't open, try clicking again + if (!dialogVisible) { + await this.page.waitForTimeout(500); + await this.syncBtn.click({ force: true }); + await dialog.waitFor({ state: 'visible', timeout: 5000 }); + } + + // Wait for dialog to be fully loaded + await this.page.waitForLoadState('networkidle'); + await this.providerSelect.waitFor({ state: 'visible', timeout: 10000 }); + + // Wait a moment for Angular animations + await this.page.waitForTimeout(500); + + // Click on provider select to open dropdown with retry + const webdavOption = this.page.locator('mat-option').filter({ hasText: 'WebDAV' }); + const selectElement = this.providerSelect; + const selectValueText = this.page.locator( + 'formly-field-mat-select .mat-mdc-select-value-text', + ); + + let selectionSucceeded = false; + + for (let attempt = 0; attempt < 5; attempt++) { + // Ensure the select is in view + await selectElement.scrollIntoViewIfNeeded({ timeout: 5000 }).catch(async () => { + // If scrollIntoViewIfNeeded fails, try scrolling the dialog content + const dialogContent = this.page.locator('mat-dialog-content'); + if (await dialogContent.isVisible()) { + await dialogContent.evaluate((el) => el.scrollTo(0, 0)); + } + }); + await this.page.waitForTimeout(300); + + // Focus and click the select element + await selectElement.focus().catch(() => {}); + await this.page.waitForTimeout(200); + + // Try multiple ways to open the dropdown + if (attempt === 0) { + await selectElement.click().catch(() => {}); + } else if (attempt === 1) { + await this.page.keyboard.press('Space'); + } else if (attempt === 2) { + await this.page.keyboard.press('ArrowDown'); + } else { + await selectElement.click({ force: true }).catch(() => {}); + } + await this.page.waitForTimeout(500); + + // Wait for any mat-option to appear (dropdown opened) + const anyOption = this.page.locator('mat-option').first(); + const anyOptionVisible = await anyOption .waitFor({ state: 'visible', timeout: 3000 }) .then(() => true) .catch(() => false); - if (webdavVisible) { - await webdavOption.click(); - - // Wait for Formly to re-render the WebDAV form fields - // (hideExpression needs time to evaluate and show the fieldGroup) - const fieldVisible = await this.baseUrlInput - .waitFor({ state: 'visible', timeout: 5000 }) + if (anyOptionVisible) { + const webdavVisible = await webdavOption + .waitFor({ state: 'visible', timeout: 3000 }) .then(() => true) .catch(() => false); - if (fieldVisible) { - break; // Form fields are visible, we can proceed + if (webdavVisible) { + await webdavOption.click(); + + // KEY FIX: Verify selection actually took effect by checking mat-select value + const selectionVerified = await selectValueText + .waitFor({ state: 'visible', timeout: 3000 }) + .then(async () => { + const text = await selectValueText.textContent(); + return text?.includes('WebDAV') ?? false; + }) + .catch(() => false); + + if (selectionVerified) { + // Selection confirmed - now wait for Formly to process the model change + await this.page.waitForTimeout(500); // Allow Formly hideExpression to evaluate + + // Wait for WebDAV form fields with increased timeout + const fieldVisible = await this.baseUrlInput + .waitFor({ state: 'visible', timeout: 8000 }) + .then(() => true) + .catch(() => false); + + if (fieldVisible) { + selectionSucceeded = true; + break; // Form fields are visible, we can proceed + } + } } - // If fields didn't appear, close any open dropdown and retry } + + // Close dropdown and retry + await this.page.keyboard.press('Escape'); + await this.page.waitForTimeout(500); } - // Close dropdown and retry + if (selectionSucceeded) { + // Final verification with increased timeout + await this.baseUrlInput.waitFor({ state: 'visible', timeout: 15000 }); + + // Fill in the configuration + await this.baseUrlInput.fill(config.baseUrl); + await this.userNameInput.fill(config.username); + await this.passwordInput.fill(config.password); + await this.syncFolderInput.fill(config.syncFolderPath); + + // Save the configuration + await this.saveBtn.click(); + + // Wait for dialog to close + await this.page.waitForTimeout(500); + return; // Success - exit the method + } + + // Selection failed after all attempts - close dialog and retry from scratch + console.log( + '[setupWebdavSync] All dropdown attempts failed, closing dialog to retry', + ); await this.page.keyboard.press('Escape'); - await this.page.waitForTimeout(500); + await this.page.waitForTimeout(1000); + + // Ensure dialog is closed before retrying + await dialog.waitFor({ state: 'hidden', timeout: 5000 }).catch(() => {}); } - // Final check - form fields should be visible now - await this.baseUrlInput.waitFor({ state: 'visible', timeout: 10000 }); - - // Fill in the configuration - await this.baseUrlInput.fill(config.baseUrl); - await this.userNameInput.fill(config.username); - await this.passwordInput.fill(config.password); - await this.syncFolderInput.fill(config.syncFolderPath); - - // Save the configuration - await this.saveBtn.click(); - - // Wait for dialog to close - await this.page.waitForTimeout(500); + // If we get here, both dialog-level attempts failed + throw new Error( + '[setupWebdavSync] Failed to setup WebDAV sync after multiple dialog-level retries', + ); } async triggerSync(): Promise {