fix(e2e): stabilize flaky tests with improved waiting strategies

- Increase task creation timeout from 10s to 15s for slow renders
- Use force:true on backdrop click to bypass overlay coverage
- Replace page.evaluate() with Playwright locators in expandSection()
- Add proper condition-based waiting for collapsible panel visibility
This commit is contained in:
Johannes Millan 2026-01-14 13:20:31 +01:00
parent c6028b980d
commit 9d21fa1b6e
2 changed files with 35 additions and 26 deletions

View file

@ -67,19 +67,25 @@ export abstract class BasePage {
if (!dialogExists) {
// Wait for task to be created - check for the specific task
const taskLocator = this.page.locator(
`task:has-text("${prefixedTaskName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}")`,
);
const maxWaitTime = 15000; // Increased from 10s to handle slow renders
const taskSelector = `task:has-text("${prefixedTaskName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}")`;
try {
await taskLocator.first().waitFor({ state: 'visible', timeout: 10000 });
// Primary: wait for the specific task to be visible
await this.page.locator(taskSelector).first().waitFor({
state: 'visible',
timeout: maxWaitTime,
});
} catch (error) {
// If specific task not found, verify count increased
// Fallback: verify task count increased (captures edge cases)
const finalCount = await this.page.locator('task').count();
if (finalCount < expectedCount) {
// Get fresh snapshot for error message after DOM settles
await this.page.waitForTimeout(500);
const tasks = await this.page.locator('task').allTextContents();
const currentCount = await this.page.locator('task').count();
throw new Error(
`Task creation failed. Expected ${expectedCount} tasks, but got ${finalCount}.\n` +
`Task creation failed. Expected ${expectedCount} tasks, but got ${currentCount}.\n` +
`Task name: "${prefixedTaskName}"\n` +
`Existing tasks: ${JSON.stringify(tasks, null, 2)}`,
);
@ -88,11 +94,12 @@ export abstract class BasePage {
}
if (!skipClose) {
// Close the add task bar if backdrop is visible
// Close the add task bar by clicking the backdrop
// Use force: true to bypass element coverage checks (overlays may cover backdrop)
const backdropVisible = await safeIsVisible(this.backdrop);
if (backdropVisible) {
await this.backdrop.click();
await this.backdrop.waitFor({ state: 'hidden', timeout: 2000 }).catch(() => {
await this.backdrop.click({ force: true });
await this.backdrop.waitFor({ state: 'hidden', timeout: 3000 }).catch(() => {
// Non-fatal: backdrop might auto-hide
});
}

View file

@ -42,25 +42,23 @@ export class SettingsPage extends BasePage {
* Expand a collapsible section by scrolling to it and clicking header
*/
async expandSection(sectionSelector: string): Promise<void> {
await this.page.evaluate((selector) => {
const section = document.querySelector(selector);
if (section) {
section.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
const section = this.page.locator(sectionSelector);
await section.scrollIntoViewIfNeeded();
const collapsible = section?.querySelector('collapsible');
if (collapsible) {
const isExpanded = collapsible.classList.contains('isExpanded');
if (!isExpanded) {
const header = collapsible.querySelector('.collapsible-header');
if (header) {
(header as HTMLElement).click();
}
}
}
}, sectionSelector);
const collapsible = section.locator('collapsible');
const isExpanded = await collapsible.evaluate((el) =>
el.classList.contains('isExpanded'),
);
if (!isExpanded) {
const header = collapsible.locator('.collapsible-header');
await header.click();
// Wait for expansion - panel only exists when expanded (@if in template)
await collapsible
.locator('.collapsible-panel')
.waitFor({ state: 'visible', timeout: 5000 });
}
await this.page.waitForTimeout(500); // Wait for expansion animation
await waitForAngularStability(this.page);
}
@ -69,7 +67,11 @@ export class SettingsPage extends BasePage {
*/
async expandPluginSection(): Promise<void> {
await this.expandSection(PLUGIN_SECTION);
// Ensure plugin management component and at least file input are visible
await this.pluginManagement.waitFor({ state: 'visible', timeout: 5000 });
await this.page
.locator(PLUGIN_FILE_INPUT)
.waitFor({ state: 'attached', timeout: 5000 });
}
/**