test(e2e): improve

This commit is contained in:
Johannes Millan 2025-08-04 18:35:00 +02:00
parent e22fa4ac5f
commit 1a82aa905b
10 changed files with 221 additions and 65 deletions

View file

@ -70,7 +70,9 @@ export const test = base.extend<TestFixtures>({
await page.waitForSelector(selector);
await page.waitForTimeout(100);
} else {
await page.waitForTimeout(500);
// Wait for the main app container to be stable
await page.locator('.route-wrapper').waitFor({ state: 'visible' });
await page.waitForTimeout(200);
}
};
await use(waitForNav);

View file

@ -18,7 +18,10 @@ export const waitForPluginAssets = async (
retryDelay = 3000;
console.log('[Plugin Test] CI environment detected, using extended timeouts');
// Wait for server to be fully ready in CI
await page.waitForTimeout(10000);
await page.waitForLoadState('networkidle');
await page.locator('app-root').waitFor({ state: 'visible', timeout: 15000 });
// Small delay for UI to stabilize
await page.waitForTimeout(200);
}
const baseUrl = page.url().split('#')[0];
@ -70,6 +73,7 @@ export const waitForPluginAssets = async (
}
if (i < maxRetries - 1) {
// Wait before retry - network request, so timeout is appropriate here
await page.waitForTimeout(retryDelay);
}
}

View file

@ -33,11 +33,22 @@ export abstract class BasePage {
await inputEl.clear();
await inputEl.fill(prefixedTaskName);
// Store the initial count BEFORE submitting
const initialCount = await this.page.locator('task').count();
const submitBtn = this.page.locator('.e2e-add-task-submit');
await submitBtn.waitFor({ state: 'visible' });
await submitBtn.click();
// wait two frames
await this.page.waitForTimeout(120);
// Wait for task count to increase
await this.page.waitForFunction(
(expectedCount) => document.querySelectorAll('task').length > expectedCount,
initialCount,
{ timeout: 10000 },
);
// Small delay to ensure task is fully rendered
await this.page.waitForTimeout(100);
if (!skipClose) {
// Only click backdrop once if it's visible

View file

@ -128,7 +128,9 @@ export class ProjectPage extends BasePage {
// Wait for the page to be fully loaded
await this.page.waitForLoadState('networkidle');
await this.page.waitForTimeout(1000);
// Wait for project view to be ready
await this.page.locator('.page-project').waitFor({ state: 'visible' });
await this.page.waitForTimeout(100);
// First ensure notes section is visible by clicking toggle if needed
const toggleNotesBtn = this.page.locator('.e2e-toggle-notes-btn');
@ -137,7 +139,8 @@ export class ProjectPage extends BasePage {
.catch(() => false);
if (isToggleBtnVisible) {
await toggleNotesBtn.click();
await this.page.waitForTimeout(1000);
// Wait for notes section to appear after toggle
await this.page.locator('notes').waitFor({ state: 'visible', timeout: 5000 });
}
// Try multiple approaches to open the note dialog
@ -162,7 +165,10 @@ export class ProjectPage extends BasePage {
}
// Wait for dialog to appear with better error handling
await this.page.waitForTimeout(1500);
await this.page.locator('dialog-fullscreen-markdown, mat-dialog-container').waitFor({
state: 'visible',
timeout: 5000,
});
// Try different selectors for the textarea
let noteTextarea = this.page.locator('dialog-fullscreen-markdown textarea').first();
@ -202,7 +208,10 @@ export class ProjectPage extends BasePage {
}
// Wait for dialog to close
await this.page.waitForTimeout(1000);
await this.page.locator('dialog-fullscreen-markdown, mat-dialog-container').waitFor({
state: 'hidden',
timeout: 5000,
});
// After saving, check if notes panel is visible
// If not, toggle it

View file

@ -11,12 +11,30 @@ test.describe('Autocomplete Dropdown', () => {
// Add task with tag syntax, skipClose=true to keep input open
await workViewPage.addTask('some task <3 #basicTag', true);
// Wait for and click the confirm create tag button
await page.waitForSelector(CONFIRM_CREATE_TAG_BTN, { state: 'visible' });
await page.click(CONFIRM_CREATE_TAG_BTN);
// Small delay to let the tag creation dialog appear
await page.waitForTimeout(500);
// Wait for tag to be created
await page.waitForSelector(BASIC_TAG_TITLE, { state: 'visible' });
// Wait for and click the confirm create tag button with increased timeout
await page.waitForSelector(CONFIRM_CREATE_TAG_BTN, {
state: 'visible',
timeout: 15000,
});
await page.locator(CONFIRM_CREATE_TAG_BTN).click();
// Wait for dialog to close
await page.waitForSelector(CONFIRM_CREATE_TAG_BTN, {
state: 'hidden',
timeout: 10000,
});
// Close the add task input if still open
const backdrop = page.locator('.backdrop');
if (await backdrop.isVisible()) {
await backdrop.click();
}
// Wait for tag to be created with increased timeout
await page.waitForSelector(BASIC_TAG_TITLE, { state: 'visible', timeout: 15000 });
// Assert tag is present and has correct text
const tagTitle = page.locator(BASIC_TAG_TITLE);

View file

@ -21,16 +21,24 @@ test.describe('Daily Summary', () => {
await workViewPage.waitForTaskList();
// Add task
await workViewPage.addTask('test task hohoho 1h/1h');
const taskName = 'test task hohoho 1h/1h';
await workViewPage.addTask(taskName);
// Wait a moment for task to be saved
await page.waitForTimeout(500);
// Navigate to daily summary
await page.goto('/#/tag/TODAY/daily-summary');
// Wait for task element in summary table
await page.waitForSelector(SUMMARY_TABLE_TASK_EL, { state: 'visible' });
await page.waitForSelector(SUMMARY_TABLE_TASK_EL, {
state: 'visible',
timeout: 15000,
});
// Assert task appears in summary
// Assert task appears in summary (look for partial match of the task name)
const taskElement = page.locator(SUMMARY_TABLE_TASK_EL);
await expect(taskElement).toContainText('test task hohoho');
// Just check for a key part of the task name that would be present regardless of prefix
await expect(taskElement).toContainText('hohoho', { timeout: 5000 });
});
});

View file

@ -13,7 +13,7 @@ const SETTINGS_BTN = `${SIDENAV} .tour-settingsMenuBtn`;
const PLUGIN_MENU = `${SIDENAV} plugin-menu`;
const PLUGIN_MENU_ITEM = `${PLUGIN_MENU} button`;
test.describe.serial('Plugin Lifecycle', () => {
test.describe('Plugin Lifecycle', () => {
test.beforeEach(async ({ page, workViewPage }) => {
const timeoutMultiplier = getCITimeoutMultiplier();
test.setTimeout(60000 * timeoutMultiplier);
@ -43,7 +43,9 @@ test.describe.serial('Plugin Lifecycle', () => {
await settingsBtn.waitFor({ state: 'visible' });
await settingsBtn.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(100);
// Wait for settings page to be fully visible
await page.locator('.page-settings').waitFor({ state: 'visible' });
await page.waitForTimeout(50); // Small delay for UI settling
await page.evaluate(() => {
const configPage = document.querySelector('.page-settings');
@ -69,8 +71,9 @@ test.describe.serial('Plugin Lifecycle', () => {
}
});
await page.waitForTimeout(100);
await expect(page.locator('plugin-management')).toBeVisible({ timeout: 5000 });
// Wait for plugin management section to be visible
await page.locator('plugin-management').waitFor({ state: 'visible', timeout: 5000 });
await page.waitForTimeout(50); // Small delay for UI settling
// Enable the plugin
const enableResult = await page.evaluate((pluginName: string) => {
@ -104,13 +107,15 @@ test.describe.serial('Plugin Lifecycle', () => {
console.log(`Plugin "API Test Plugin" enable state:`, enableResult);
expect(enableResult.found).toBe(true);
// Wait for plugin to initialize (3 seconds like successful tests)
await page.waitForTimeout(100);
// Wait for plugin to initialize
await page.waitForTimeout(100); // Small delay for plugin initialization
// Go back to work view
await page.goto('/#/tag/TODAY');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(100);
// Wait for work view to be ready
await page.locator('.route-wrapper').waitFor({ state: 'visible' });
await page.waitForTimeout(50); // Small delay for UI settling
// Wait for task list to be visible
await page.waitForSelector('task-list', { state: 'visible', timeout: 10000 });
@ -118,7 +123,9 @@ test.describe.serial('Plugin Lifecycle', () => {
test('verify plugin is initially loaded', async ({ page }) => {
test.setTimeout(20000); // Increase timeout
await page.waitForTimeout(100); // Wait for plugins to initialize
// Wait for plugin menu to be ready
await page.locator(PLUGIN_MENU).waitFor({ state: 'visible' });
await page.waitForTimeout(50); // Small delay for plugins to initialize
// Plugin doesn't show snack bar on load, check plugin menu instead
await expect(page.locator(PLUGIN_MENU_ITEM)).toBeVisible({ timeout: 10000 });
@ -131,7 +138,9 @@ test.describe.serial('Plugin Lifecycle', () => {
// Click on the plugin menu item to navigate to plugin
await expect(page.locator(PLUGIN_MENU_ITEM)).toBeVisible();
await page.click(PLUGIN_MENU_ITEM);
await page.waitForTimeout(100);
// Wait for navigation to complete
await page.waitForLoadState('networkidle');
await page.waitForTimeout(50); // Small delay for UI settling
// Verify we navigated to the plugin page
await expect(page).toHaveURL(/\/plugins\/api-test-plugin\/index/);
@ -144,10 +153,13 @@ test.describe.serial('Plugin Lifecycle', () => {
test('disable plugin and verify cleanup', async ({ page, workViewPage }) => {
test.setTimeout(30000); // Increase timeout
// First enable the plugin
// Navigate to settings
await page.click(SETTINGS_BTN);
await page.waitForTimeout(100);
await page.waitForLoadState('networkidle');
await page.locator('.page-settings').waitFor({ state: 'visible' });
await page.waitForTimeout(200); // Small delay for UI settling
// Expand plugin section
await page.evaluate(() => {
const pluginSection = document.querySelector('.plugin-section');
if (pluginSection) {
@ -163,10 +175,12 @@ test.describe.serial('Plugin Lifecycle', () => {
}
});
await page.waitForTimeout(100);
// Wait for plugin management to be ready
await page.locator('plugin-management').waitFor({ state: 'visible', timeout: 10000 });
await page.waitForTimeout(500); // Give time for plugins to load
// Enable the plugin first
await page.evaluate((pluginName: string) => {
// Check current state of the plugin and enable if needed
const currentState = await page.evaluate((pluginName: string) => {
const cards = Array.from(document.querySelectorAll('plugin-management mat-card'));
const targetCard = cards.find((card) => {
const title = card.querySelector('mat-card-title')?.textContent || '';
@ -177,16 +191,44 @@ test.describe.serial('Plugin Lifecycle', () => {
const toggleButton = targetCard.querySelector(
'mat-slide-toggle button[role="switch"]',
) as HTMLButtonElement;
if (toggleButton && toggleButton.getAttribute('aria-checked') !== 'true') {
toggleButton.click();
if (toggleButton) {
const isEnabled = toggleButton.getAttribute('aria-checked') === 'true';
if (!isEnabled) {
toggleButton.click();
return { found: true, wasEnabled: false, clicked: true };
}
return { found: true, wasEnabled: true, clicked: false };
}
}
return { found: false };
}, 'API Test Plugin');
await page.waitForTimeout(100); // Wait for plugin to enable
console.log('Plugin state before disable:', currentState);
// Find and disable the API Test Plugin
await page.evaluate((pluginName: string) => {
// If we just enabled it, wait for it to be enabled
if (currentState.clicked) {
await page.waitForFunction(
(name) => {
const cards = Array.from(
document.querySelectorAll('plugin-management mat-card'),
);
const targetCard = cards.find((card) => {
const title = card.querySelector('mat-card-title')?.textContent || '';
return title.includes(name);
});
const toggle = targetCard?.querySelector(
'mat-slide-toggle button[role="switch"]',
) as HTMLButtonElement;
return toggle?.getAttribute('aria-checked') === 'true';
},
'API Test Plugin',
{ timeout: 5000 },
);
await page.waitForTimeout(1000); // Wait for plugin to fully initialize
}
// Now disable the plugin
const disableResult = await page.evaluate((pluginName: string) => {
const cards = Array.from(document.querySelectorAll('plugin-management mat-card'));
const targetCard = cards.find((card) => {
const title = card.querySelector('mat-card-title')?.textContent || '';
@ -199,22 +241,53 @@ test.describe.serial('Plugin Lifecycle', () => {
) as HTMLButtonElement;
if (toggleButton && toggleButton.getAttribute('aria-checked') === 'true') {
toggleButton.click();
return { found: true, clicked: true };
}
return { found: true, clicked: false, alreadyDisabled: true };
}
return { found: false };
}, 'API Test Plugin');
await page.waitForTimeout(100); // Wait for plugin to disable
console.log('Disable result:', disableResult);
// Go back and verify menu entry is removed
// Wait for toggle state to update to disabled
await page.waitForFunction(
(name) => {
const cards = Array.from(document.querySelectorAll('plugin-management mat-card'));
const targetCard = cards.find((card) => {
const title = card.querySelector('mat-card-title')?.textContent || '';
return title.includes(name);
});
const toggle = targetCard?.querySelector(
'mat-slide-toggle button[role="switch"]',
) as HTMLButtonElement;
return toggle?.getAttribute('aria-checked') === 'false';
},
'API Test Plugin',
{ timeout: 5000 },
);
await page.waitForTimeout(1000); // Wait for plugin to fully disable
// Go back to work view
await page.goto('/#/tag/TODAY');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(100);
await page.locator('.route-wrapper').waitFor({ state: 'visible' });
await page.waitForTimeout(500); // Small delay for UI settling
// Reload to ensure plugin state is refreshed
await page.reload();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(100);
// Check if the plugin menu exists and verify the API Test Plugin is not in it
const pluginMenuExists = (await page.locator(PLUGIN_MENU).count()) > 0;
await expect(page.locator(PLUGIN_MENU_ITEM)).not.toBeVisible();
if (pluginMenuExists) {
// Check all plugin menu items to ensure API Test Plugin is not present
const hasApiTestPlugin = await page.evaluate(() => {
const menuItems = Array.from(document.querySelectorAll('plugin-menu button'));
return menuItems.some((item) => item.textContent?.includes('API Test Plugin'));
});
expect(hasApiTestPlugin).toBe(false);
} else {
// Plugin menu doesn't exist at all, which is also valid when no plugins are enabled
expect(pluginMenuExists).toBe(false);
}
});
});

View file

@ -51,19 +51,22 @@ test.describe('Reminders View Task', () => {
// Wait for dialog
await page.waitForSelector(DIALOG_CONTAINER, { state: 'visible' });
await page.waitForTimeout(100);
await page.locator(DIALOG_CONTAINER).waitFor({ state: 'visible' });
await page.waitForTimeout(50); // Small delay for dialog animation
// Set time
await page.waitForSelector(TIME_INP, { state: 'visible' });
await page.waitForTimeout(150);
await page.locator(TIME_INP).waitFor({ state: 'visible' });
await page.waitForTimeout(50); // Small delay for UI settling
// Focus and set time value
await page.click(TIME_INP);
await page.waitForTimeout(150);
await page.locator(TIME_INP).focus();
await page.waitForTimeout(50); // Small delay for focus
// Clear and set value
await page.fill(TIME_INP, '');
await page.waitForTimeout(100);
await page.waitForTimeout(50); // Small delay for clear
// Set the time value
await page.evaluate(
@ -78,15 +81,17 @@ test.describe('Reminders View Task', () => {
{ selector: TIME_INP, value: timeValue },
);
await page.waitForTimeout(200);
// Wait for value to be updated in the input
await page.locator(TIME_INP).waitFor({ state: 'visible' });
await page.waitForTimeout(50); // Small delay for value update
// Also set value normally
await page.fill(TIME_INP, timeValue);
await page.waitForTimeout(200);
await page.waitForTimeout(50); // Small delay for value setting
// Tab to commit value
await page.keyboard.press('Tab');
await page.waitForTimeout(200);
await page.waitForTimeout(50); // Small delay for tab action
// Submit dialog
await page.waitForSelector(DIALOG_SUBMIT, { state: 'visible' });

View file

@ -11,12 +11,15 @@ test.describe('Short Syntax', () => {
// Add a task with project short syntax
await workViewPage.addTask('0 test task koko +i');
// Wait a moment for the task to be processed
await page.waitForTimeout(500);
// Verify task is visible
const task = page.locator('task').first();
await expect(task).toBeVisible();
await expect(task).toBeVisible({ timeout: 10000 });
// Verify the task has the Inbox tag
const taskTags = task.locator('tag');
await expect(taskTags).toContainText('Inbox');
await expect(taskTags).toContainText('Inbox', { timeout: 5000 });
});
});

View file

@ -76,25 +76,48 @@ test.describe('Work View', () => {
});
test('should add 2 tasks from initial bar', async ({ page, workViewPage }) => {
test.setTimeout(20000);
test.setTimeout(30000); // Increase timeout
// Wait for work view to be ready
await workViewPage.waitForTaskList();
await page.waitForTimeout(2000); // Wait for UI to stabilize
await page.waitForTimeout(1000); // Give UI time to fully initialize
// Simply add two tasks using the standard method
await workViewPage.addTask('2 test task hihi');
await page.waitForTimeout(500);
await workViewPage.addTask('3 some other task');
await page.waitForTimeout(500);
// Add two tasks - the addTask method now properly waits for each one
await workViewPage.addTask('test task hihi');
await workViewPage.addTask('some other task here');
// Verify both tasks are visible
const tasks = page.locator('task');
await expect(tasks).toHaveCount(2);
await expect(tasks).toHaveCount(2, { timeout: 10000 });
// Verify task order (most recent first due to global add)
await expect(tasks.nth(0).locator('textarea')).toHaveValue(/.*3 some other task/);
await expect(tasks.nth(1).locator('textarea')).toHaveValue(/.*2 test task hihi/);
// Get all task textareas and their values
const taskTextareas = await tasks.locator('textarea').all();
const taskContents: string[] = [];
for (const textarea of taskTextareas) {
try {
const value = await textarea.inputValue();
taskContents.push(value);
} catch (e) {
console.log('Failed to get textarea value:', e);
}
}
// Debug log to see what we actually have
console.log('Number of tasks found:', await tasks.count());
console.log('Task contents found:', taskContents);
// Check that both tasks are present (look for key parts that would be in any version)
const hasHihi = taskContents.some((v) => v.includes('hihi'));
const hasOther = taskContents.some((v) => v.includes('other task'));
// More detailed assertion for debugging
if (!hasHihi || !hasOther) {
console.log('Missing expected tasks. Found:', taskContents);
console.log('hasHihi:', hasHihi, 'hasOther:', hasOther);
}
expect(hasHihi).toBe(true);
expect(hasOther).toBe(true);
});
});