mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
test(e2e): fix 12 flaky tests by removing networkidle waits and fixing plugin setup
Replace unreliable waitForLoadState('networkidle') with element-based waits
in planner page and tests. Fix plugin helper timeout math bugs and eliminate
duplicate settings navigation in plugin tests. Improve menu overlay cleanup
and increase tight timeouts in migration and work-view tests.
This commit is contained in:
parent
a416661bf6
commit
b834bbedb1
10 changed files with 67 additions and 339 deletions
|
|
@ -81,24 +81,19 @@ export const waitForPluginAssets = async (
|
|||
*/
|
||||
export const waitForPluginManagementInit = async (
|
||||
page: Page,
|
||||
timeout: number = 15000, // Reduced from 20s to 15s // Reduced from 30s to 20s
|
||||
timeout: number = 30000,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
// First ensure we're on the settings page and plugin section is expanded
|
||||
// Navigate to settings page if not already there
|
||||
const currentUrl = page.url();
|
||||
if (!currentUrl.includes('#/config')) {
|
||||
await page.click('text=Settings');
|
||||
await page.waitForURL(/.*#\/config.*/, { timeout: 8000 }); // Reduced from 10s to 8s
|
||||
await page.goto('/#/config');
|
||||
}
|
||||
|
||||
// Wait for settings page to load
|
||||
await page.waitForSelector('.page-settings', { state: 'visible', timeout: 8000 }); // Reduced from 10s to 8s
|
||||
await page.waitForSelector('.page-settings', { state: 'visible', timeout: 10000 });
|
||||
|
||||
// Wait a bit for the page to stabilize
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Navigate to the Plugins tab (4th tab, index 3)
|
||||
// The plugin section is now inside the Plugins tab, not directly on the page
|
||||
// Navigate to the Plugins tab
|
||||
await page.evaluate(() => {
|
||||
const pluginsTab = Array.from(
|
||||
document.querySelectorAll('mat-tab-header .mat-mdc-tab'),
|
||||
|
|
@ -112,10 +107,8 @@ export const waitForPluginManagementInit = async (
|
|||
}
|
||||
});
|
||||
|
||||
// Wait for tab content to load
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Now expand plugin section if collapsed and scroll it into view
|
||||
// Expand plugin section if collapsed and scroll it into view
|
||||
await page.waitForSelector('.plugin-section', { state: 'visible', timeout: 5000 });
|
||||
await page.evaluate(() => {
|
||||
const pluginSection = document.querySelector('.plugin-section');
|
||||
if (pluginSection) {
|
||||
|
|
@ -131,10 +124,21 @@ export const waitForPluginManagementInit = async (
|
|||
}
|
||||
});
|
||||
|
||||
// Wait for expansion animation and scroll to complete
|
||||
await page.waitForTimeout(300);
|
||||
// Wait for plugin management component with plugin cards
|
||||
await page.waitForSelector('plugin-management', {
|
||||
state: 'attached',
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// Scroll plugin-management into view explicitly
|
||||
const result = await page.waitForFunction(
|
||||
() => {
|
||||
const cards = document.querySelectorAll('plugin-management mat-card');
|
||||
return cards.length > 0;
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
|
||||
// Scroll plugin-management into view
|
||||
await page.evaluate(() => {
|
||||
const pluginMgmt = document.querySelector('plugin-management');
|
||||
if (pluginMgmt) {
|
||||
|
|
@ -142,24 +146,6 @@ export const waitForPluginManagementInit = async (
|
|||
}
|
||||
});
|
||||
|
||||
// Wait for plugin management component to be attached (not necessarily visible)
|
||||
await page.waitForSelector('plugin-management', {
|
||||
state: 'attached',
|
||||
timeout: Math.max(5000, timeout - 20000),
|
||||
});
|
||||
|
||||
// Additional check for plugin cards to be loaded
|
||||
const result = await page.waitForFunction(
|
||||
() => {
|
||||
const pluginMgmt = document.querySelector('plugin-management');
|
||||
if (!pluginMgmt) return false;
|
||||
|
||||
const cards = document.querySelectorAll('plugin-management mat-card');
|
||||
return cards.length > 0;
|
||||
},
|
||||
{ timeout: Math.max(5000, timeout - 25000) },
|
||||
);
|
||||
|
||||
return !!result;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
|
|
|
|||
|
|
@ -25,18 +25,17 @@ export class PlannerPage extends BasePage {
|
|||
// Try to click the planner nav item first, fallback to direct navigation
|
||||
try {
|
||||
await this.page.locator('magic-side-nav a[href="#/planner"]').click();
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
} catch (error) {
|
||||
// Fallback to direct navigation
|
||||
await this.page.goto('/#/tag/TODAY/planner');
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
await this.page.waitForURL(/\/(planner|tasks)/);
|
||||
await this.routerWrapper.waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
async navigateToPlannerForProject(projectId: string): Promise<void> {
|
||||
await this.page.goto(`/#/project/${projectId}/planner`);
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
await this.page.waitForURL(/\/(planner|tasks)/);
|
||||
await this.routerWrapper.waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,7 +120,12 @@ export class TagPage extends BasePage {
|
|||
await tagNameInput.waitFor({ state: 'hidden', timeout: 3000 });
|
||||
}
|
||||
|
||||
// Wait for all overlays to close to ensure clean state for next operation
|
||||
// Wait for all menus and overlays to fully close before returning
|
||||
await this.page
|
||||
.locator('.mat-mdc-menu-panel')
|
||||
.first()
|
||||
.waitFor({ state: 'detached', timeout: 3000 })
|
||||
.catch(() => {});
|
||||
await this.ensureOverlaysClosed();
|
||||
|
||||
// Wait for the tag to actually appear on the task
|
||||
|
|
|
|||
|
|
@ -19,27 +19,28 @@ test.describe('@migration Pre-migration Dialog', () => {
|
|||
page,
|
||||
workViewPage,
|
||||
}) => {
|
||||
test.setTimeout(45000);
|
||||
|
||||
// Wait for app to be ready - this implicitly verifies migration completed
|
||||
await workViewPage.waitForTaskList();
|
||||
|
||||
// Create a task to verify app is fully functional
|
||||
await workViewPage.addTask('Post-migration test task');
|
||||
await page.waitForSelector('task', { state: 'visible' });
|
||||
|
||||
// Verify the task was created
|
||||
// Verify the task was created (addTask already waits for visibility)
|
||||
await expect(page.locator('task-title').first()).toContainText(
|
||||
'Post-migration test task',
|
||||
);
|
||||
|
||||
// Create another task to ensure state management works
|
||||
await workViewPage.addTask('Second post-migration task');
|
||||
await expect(page.locator('task')).toHaveCount(2);
|
||||
await expect(page.locator('task')).toHaveCount(2, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('dialog-confirm should show both buttons by default', async ({ page }) => {
|
||||
// Navigate to settings and trigger a dialog that shows both buttons
|
||||
await page.goto('/#/settings');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
// Find and click a section that might trigger a confirm dialog
|
||||
// We'll use the "Clear Storage" option if available
|
||||
|
|
|
|||
|
|
@ -15,9 +15,8 @@ test.describe('Planner Time Estimates', () => {
|
|||
}) => {
|
||||
// Add task with time estimate using short syntax
|
||||
await workViewPage.addTask('Important task /2h/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Verify task was created
|
||||
// Verify task was created (auto-retries until count matches)
|
||||
await expect(page.locator('task')).toHaveCount(1);
|
||||
|
||||
// Task should contain the time estimate in title
|
||||
|
|
@ -32,9 +31,8 @@ test.describe('Planner Time Estimates', () => {
|
|||
await workViewPage.addTask('First task /1h/');
|
||||
await workViewPage.addTask('Second task /30m/');
|
||||
await workViewPage.addTask('Third task /2h/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Verify all tasks created
|
||||
// Verify all tasks created (auto-retries until count matches)
|
||||
await expect(page.locator('task')).toHaveCount(3);
|
||||
|
||||
// Navigate to planner
|
||||
|
|
@ -51,7 +49,6 @@ test.describe('Planner Time Estimates', () => {
|
|||
}) => {
|
||||
// Add task with time estimate syntax
|
||||
await workViewPage.addTask('Development work /4h/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Navigate to planner
|
||||
await plannerPage.navigateToPlanner();
|
||||
|
|
@ -69,7 +66,6 @@ test.describe('Planner Time Estimates', () => {
|
|||
// Add tasks with various time formats
|
||||
await workViewPage.addTask('Quick fix /15m/');
|
||||
await workViewPage.addTask('Feature development /3h30m/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Navigate to planner
|
||||
await plannerPage.navigateToPlanner();
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
import { expect, test } from '../../fixtures/test.fixture';
|
||||
import { cssSelectors } from '../../constants/selectors';
|
||||
import {
|
||||
getCITimeoutMultiplier,
|
||||
waitForPluginAssets,
|
||||
waitForPluginManagementInit,
|
||||
} from '../../helpers/plugin-test.helpers';
|
||||
|
||||
const { SETTINGS_BTN } = cssSelectors;
|
||||
|
||||
test.describe('Enable Plugin Test', () => {
|
||||
test('navigate to plugin settings and enable API Test Plugin', async ({
|
||||
page,
|
||||
workViewPage,
|
||||
}) => {
|
||||
const timeoutMultiplier = getCITimeoutMultiplier();
|
||||
test.setTimeout(30000 * timeoutMultiplier); // Reduced from 60s to 30s base
|
||||
|
||||
// console.log('[Plugin Test] Starting enable plugin test...');
|
||||
test.setTimeout(30000 * timeoutMultiplier);
|
||||
|
||||
// First, ensure plugin assets are available
|
||||
const assetsAvailable = await waitForPluginAssets(page);
|
||||
|
|
@ -30,77 +25,19 @@ test.describe('Enable Plugin Test', () => {
|
|||
|
||||
await workViewPage.waitForTaskList();
|
||||
|
||||
// Navigate to settings and initialize plugin management
|
||||
// This navigates to settings, selects plugin tab, and expands plugin section
|
||||
await waitForPluginManagementInit(page);
|
||||
|
||||
// Navigate to plugin settings
|
||||
await page.click(SETTINGS_BTN);
|
||||
await page
|
||||
.locator('.page-settings')
|
||||
.first()
|
||||
.waitFor({ state: 'visible', timeout: 10000 });
|
||||
|
||||
await page.evaluate(() => {
|
||||
const configPage = document.querySelector('.page-settings');
|
||||
if (!configPage) {
|
||||
console.error('Not on config page');
|
||||
return;
|
||||
}
|
||||
|
||||
const pluginSection = document.querySelector('.plugin-section');
|
||||
if (pluginSection) {
|
||||
pluginSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
} else {
|
||||
console.error('Plugin section not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const collapsible = document.querySelector('.plugin-section collapsible');
|
||||
if (collapsible) {
|
||||
const isExpanded = collapsible.classList.contains('isExpanded');
|
||||
if (!isExpanded) {
|
||||
const header = collapsible.querySelector('.collapsible-header');
|
||||
if (header) {
|
||||
(header as HTMLElement).click();
|
||||
// console.log('Clicked to expand plugin collapsible');
|
||||
} else {
|
||||
console.error('Could not find collapsible header');
|
||||
}
|
||||
} else {
|
||||
// console.log('Plugin collapsible already expanded');
|
||||
}
|
||||
} else {
|
||||
console.error('Plugin collapsible not found');
|
||||
}
|
||||
});
|
||||
|
||||
await expect(page.locator('plugin-management')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Wait for plugin cards to be loaded
|
||||
await page
|
||||
.locator('plugin-management mat-card')
|
||||
.first()
|
||||
.waitFor({ state: 'attached', timeout: 10000 });
|
||||
|
||||
// Check if plugin-management has any content
|
||||
await page.evaluate(() => {
|
||||
const pluginMgmt = document.querySelector('plugin-management');
|
||||
const matCards = pluginMgmt ? pluginMgmt.querySelectorAll('mat-card') : [];
|
||||
|
||||
// Filter out warning card
|
||||
const pluginCards = Array.from(matCards).filter((card) => {
|
||||
return card.querySelector('mat-slide-toggle') !== null;
|
||||
});
|
||||
|
||||
return {
|
||||
pluginMgmtExists: !!pluginMgmt,
|
||||
totalCardCount: matCards.length,
|
||||
pluginCardCount: pluginCards.length,
|
||||
pluginCardTexts: pluginCards.map(
|
||||
(card) => card.querySelector('mat-card-title')?.textContent?.trim() || '',
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
// Try to find and enable the API Test Plugin (which exists by default)
|
||||
// Try to find and enable the API Test Plugin
|
||||
const enableResult = await page.evaluate(() => {
|
||||
const pluginCards = document.querySelectorAll('plugin-management mat-card');
|
||||
let foundApiTestPlugin = false;
|
||||
|
|
@ -128,7 +65,6 @@ test.describe('Enable Plugin Test', () => {
|
|||
};
|
||||
});
|
||||
|
||||
// console.log('Plugin enablement result:', enableResult);
|
||||
expect(enableResult.foundApiTestPlugin).toBe(true);
|
||||
|
||||
// Wait for toggle state to change to enabled
|
||||
|
|
@ -150,16 +86,5 @@ test.describe('Enable Plugin Test', () => {
|
|||
{ timeout: 10000 },
|
||||
);
|
||||
}
|
||||
|
||||
// Now check if plugin menu has buttons
|
||||
await page.evaluate(() => {
|
||||
const sideNav = document.querySelector('magic-side-nav');
|
||||
const buttons = sideNav ? sideNav.querySelectorAll('nav-item button') : [];
|
||||
return {
|
||||
sideNavExists: !!sideNav,
|
||||
buttonCount: buttons.length,
|
||||
buttonTexts: Array.from(buttons).map((btn) => btn.textContent?.trim() || ''),
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,20 +1,16 @@
|
|||
import { expect, test } from '../../fixtures/test.fixture';
|
||||
import { cssSelectors } from '../../constants/selectors';
|
||||
import {
|
||||
enablePluginWithVerification,
|
||||
getCITimeoutMultiplier,
|
||||
logPluginState,
|
||||
waitForPluginAssets,
|
||||
waitForPluginInMenu,
|
||||
waitForPluginManagementInit,
|
||||
} from '../../helpers/plugin-test.helpers';
|
||||
|
||||
const { SETTINGS_BTN } = cssSelectors;
|
||||
|
||||
test.describe.serial('Plugin Enable Verify', () => {
|
||||
test('enable API Test Plugin and verify menu entry', async ({ page, workViewPage }) => {
|
||||
const timeoutMultiplier = getCITimeoutMultiplier();
|
||||
test.setTimeout(30000 * timeoutMultiplier); // Reduced from 60s to 30s base
|
||||
test.setTimeout(30000 * timeoutMultiplier);
|
||||
|
||||
// First, ensure plugin assets are available
|
||||
const assetsAvailable = await waitForPluginAssets(page);
|
||||
|
|
@ -28,6 +24,7 @@ test.describe.serial('Plugin Enable Verify', () => {
|
|||
|
||||
await workViewPage.waitForTaskList();
|
||||
|
||||
// Navigate to settings and initialize plugin management
|
||||
const initSuccess = await waitForPluginManagementInit(page);
|
||||
if (!initSuccess) {
|
||||
throw new Error(
|
||||
|
|
@ -35,45 +32,11 @@ test.describe.serial('Plugin Enable Verify', () => {
|
|||
);
|
||||
}
|
||||
|
||||
// Navigate to plugin settings
|
||||
await page.click(SETTINGS_BTN);
|
||||
await page.waitForSelector('.page-settings', {
|
||||
state: 'visible',
|
||||
timeout: 10000 * timeoutMultiplier,
|
||||
});
|
||||
|
||||
// Expand plugin section
|
||||
await page.evaluate(() => {
|
||||
const pluginSection = document.querySelector('.plugin-section');
|
||||
if (pluginSection) {
|
||||
pluginSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
const collapsible = document.querySelector('.plugin-section collapsible');
|
||||
if (collapsible && !collapsible.classList.contains('isExpanded')) {
|
||||
const header = collapsible.querySelector('.collapsible-header');
|
||||
if (header) {
|
||||
(header as HTMLElement).click();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for plugin management to be visible
|
||||
await page.waitForSelector('plugin-management', {
|
||||
state: 'visible',
|
||||
timeout: 10000 * timeoutMultiplier,
|
||||
});
|
||||
|
||||
// Log current plugin state for debugging
|
||||
if (process.env.CI) {
|
||||
await logPluginState(page);
|
||||
}
|
||||
|
||||
// Enable API Test Plugin with verification
|
||||
// Plugin management is now visible - enable API Test Plugin
|
||||
const pluginEnabled = await enablePluginWithVerification(
|
||||
page,
|
||||
'API Test Plugin',
|
||||
10000 * timeoutMultiplier, // Reduced from 15s to 10s
|
||||
10000 * timeoutMultiplier,
|
||||
);
|
||||
|
||||
expect(pluginEnabled).toBe(true);
|
||||
|
|
@ -82,14 +45,13 @@ test.describe.serial('Plugin Enable Verify', () => {
|
|||
const pluginInMenu = await waitForPluginInMenu(
|
||||
page,
|
||||
'API Test Plugin',
|
||||
15000 * timeoutMultiplier, // Reduced from 20s to 15s
|
||||
15000 * timeoutMultiplier,
|
||||
);
|
||||
|
||||
expect(pluginInMenu).toBe(true);
|
||||
|
||||
// Additional verification - check menu structure in magic-side-nav
|
||||
const menuResult = await page.evaluate(() => {
|
||||
// Look for plugin items in magic-side-nav structure
|
||||
const sideNav = document.querySelector('magic-side-nav');
|
||||
const navButtons = sideNav
|
||||
? Array.from(sideNav.querySelectorAll('nav-item button'))
|
||||
|
|
@ -104,7 +66,6 @@ test.describe.serial('Plugin Enable Verify', () => {
|
|||
|
||||
expect(menuResult.hasSideNav).toBe(true);
|
||||
expect(menuResult.buttonCount).toBeGreaterThan(0);
|
||||
// Check if any button text contains "API Test Plugin" (handle whitespace)
|
||||
const hasApiTestPlugin = menuResult.buttonTexts.some((text: string) =>
|
||||
text.includes('API Test Plugin'),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,29 +3,23 @@ import { cssSelectors } from '../../constants/selectors';
|
|||
import {
|
||||
enablePluginWithVerification,
|
||||
getCITimeoutMultiplier,
|
||||
logPluginState,
|
||||
waitForPluginAssets,
|
||||
waitForPluginInMenu,
|
||||
waitForPluginManagementInit,
|
||||
} from '../../helpers/plugin-test.helpers';
|
||||
|
||||
const { SIDENAV, SETTINGS_BTN } = cssSelectors;
|
||||
const { SIDENAV } = cssSelectors;
|
||||
|
||||
// Plugin-related selectors
|
||||
const PLUGIN_NAV_ITEMS = `${SIDENAV} nav-item button`;
|
||||
const PLUGIN_IFRAME = 'plugin-index iframe';
|
||||
const PLUGIN_MANAGEMENT = 'plugin-management';
|
||||
const PLUGIN_SECTION = '.plugin-section';
|
||||
const SETTINGS_PAGE = '.page-settings';
|
||||
const COLLAPSIBLE_EXPANDED = '.plugin-section collapsible.isExpanded';
|
||||
|
||||
test.describe.serial('Plugin Iframe', () => {
|
||||
test.beforeEach(async ({ page, workViewPage }) => {
|
||||
// Increase timeout for CI environment
|
||||
const timeoutMultiplier = getCITimeoutMultiplier();
|
||||
test.setTimeout(30000 * timeoutMultiplier); // Reduced from 60s to 30s base
|
||||
test.setTimeout(30000 * timeoutMultiplier);
|
||||
|
||||
// First, ensure plugin assets are available
|
||||
// Ensure plugin assets are available
|
||||
const assetsAvailable = await waitForPluginAssets(page);
|
||||
if (!assetsAvailable) {
|
||||
if (process.env.CI) {
|
||||
|
|
@ -37,6 +31,7 @@ test.describe.serial('Plugin Iframe', () => {
|
|||
|
||||
await workViewPage.waitForTaskList();
|
||||
|
||||
// Navigate to settings and initialize plugin management
|
||||
const initSuccess = await waitForPluginManagementInit(page);
|
||||
if (!initSuccess) {
|
||||
throw new Error(
|
||||
|
|
@ -44,46 +39,11 @@ test.describe.serial('Plugin Iframe', () => {
|
|||
);
|
||||
}
|
||||
|
||||
// Navigate to settings
|
||||
const settingsBtn = page.locator(SETTINGS_BTN);
|
||||
await settingsBtn.waitFor({ state: 'visible' });
|
||||
await settingsBtn.click();
|
||||
|
||||
// Wait for settings page to be visible
|
||||
await page.waitForSelector(SETTINGS_PAGE, { state: 'visible' });
|
||||
|
||||
// Scroll to plugin section
|
||||
const pluginSection = page.locator(PLUGIN_SECTION);
|
||||
await pluginSection.scrollIntoViewIfNeeded();
|
||||
|
||||
// Expand collapsible if needed
|
||||
const collapsible = page.locator('.plugin-section collapsible');
|
||||
const collapsibleHeader = collapsible.locator('.collapsible-header');
|
||||
|
||||
// Check if already expanded, if not click to expand
|
||||
const expandedCollapsible = page.locator(COLLAPSIBLE_EXPANDED);
|
||||
if ((await expandedCollapsible.count()) === 0) {
|
||||
await collapsibleHeader.click();
|
||||
// Wait for the expanded class to appear
|
||||
await page.waitForSelector(COLLAPSIBLE_EXPANDED, { state: 'visible' });
|
||||
}
|
||||
|
||||
// Wait for plugin management to be visible
|
||||
await page.waitForSelector(PLUGIN_MANAGEMENT, {
|
||||
state: 'visible',
|
||||
timeout: 10000 * timeoutMultiplier,
|
||||
});
|
||||
|
||||
// Log current plugin state for debugging
|
||||
if (process.env.CI) {
|
||||
await logPluginState(page);
|
||||
}
|
||||
|
||||
// Enable API Test Plugin with verification
|
||||
// Enable API Test Plugin
|
||||
const pluginEnabled = await enablePluginWithVerification(
|
||||
page,
|
||||
'API Test Plugin',
|
||||
10000 * timeoutMultiplier, // Reduced from 15s to 10s
|
||||
10000 * timeoutMultiplier,
|
||||
);
|
||||
|
||||
if (!pluginEnabled) {
|
||||
|
|
@ -94,12 +54,10 @@ test.describe.serial('Plugin Iframe', () => {
|
|||
const pluginInMenu = await waitForPluginInMenu(
|
||||
page,
|
||||
'API Test Plugin',
|
||||
15000 * timeoutMultiplier, // Reduced from 20s to 15s
|
||||
15000 * timeoutMultiplier,
|
||||
);
|
||||
|
||||
if (!pluginInMenu) {
|
||||
// Log state for debugging
|
||||
await logPluginState(page);
|
||||
throw new Error('API Test Plugin not found in menu after enabling');
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +69,6 @@ test.describe.serial('Plugin Iframe', () => {
|
|||
);
|
||||
if (await cancelBtn.isVisible().catch(() => false)) {
|
||||
await cancelBtn.click();
|
||||
// Wait for dialog to disappear
|
||||
await tourDialog.waitFor({ state: 'hidden' });
|
||||
}
|
||||
}
|
||||
|
|
@ -149,13 +106,10 @@ test.describe.serial('Plugin Iframe', () => {
|
|||
await expect(iframe).toBeVisible();
|
||||
|
||||
// Try to verify iframe content if possible
|
||||
// Note: Cross-origin iframes may not allow content access
|
||||
const frameLocator = page.frameLocator(PLUGIN_IFRAME);
|
||||
try {
|
||||
// Wait for iframe body to be loaded
|
||||
await frameLocator.locator('body').waitFor({ state: 'visible' });
|
||||
|
||||
// Check for expected content if accessible
|
||||
const h1 = frameLocator.locator('h1');
|
||||
const hasH1 = (await h1.count()) > 0;
|
||||
if (hasH1) {
|
||||
|
|
@ -164,7 +118,6 @@ test.describe.serial('Plugin Iframe', () => {
|
|||
} catch (error) {
|
||||
// If iframe content is not accessible due to cross-origin restrictions,
|
||||
// at least verify the iframe element itself is present
|
||||
// console.log('Note: Iframe content verification skipped (possibly cross-origin)');
|
||||
await expect(iframe).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
getCITimeoutMultiplier,
|
||||
} from '../../helpers/plugin-test.helpers';
|
||||
|
||||
const { SIDENAV, SETTINGS_BTN } = cssSelectors;
|
||||
const { SIDENAV } = cssSelectors;
|
||||
|
||||
// Plugin-related selectors
|
||||
const API_TEST_PLUGIN_NAV_ITEM = `${SIDENAV} nav-item button:has-text("API Test Plugin")`;
|
||||
|
|
@ -18,7 +18,7 @@ const TEST_TIMEOUT_MS = 30000 * TIMEOUT_MULTIPLIER;
|
|||
|
||||
test.describe('Plugin Lifecycle', () => {
|
||||
test.beforeEach(async ({ page, workViewPage }) => {
|
||||
test.setTimeout(TEST_TIMEOUT_MS); // Reduced from 60s to 30s base
|
||||
test.setTimeout(TEST_TIMEOUT_MS);
|
||||
|
||||
// First, ensure plugin assets are available
|
||||
const assetsAvailable = await waitForPluginAssets(page);
|
||||
|
|
@ -32,6 +32,7 @@ test.describe('Plugin Lifecycle', () => {
|
|||
|
||||
await workViewPage.waitForTaskList();
|
||||
|
||||
// Navigate to settings and initialize plugin management
|
||||
const initSuccess = await waitForPluginManagementInit(page);
|
||||
if (!initSuccess) {
|
||||
throw new Error(
|
||||
|
|
@ -55,7 +56,7 @@ test.describe('Plugin Lifecycle', () => {
|
|||
});
|
||||
|
||||
test('verify plugin is initially loaded', async ({ page }) => {
|
||||
test.setTimeout(TEST_TIMEOUT_MS); // Increase timeout
|
||||
test.setTimeout(TEST_TIMEOUT_MS);
|
||||
// Wait for magic-side-nav to be ready
|
||||
await page.locator(SIDENAV).waitFor({ state: 'visible' });
|
||||
|
||||
|
|
@ -65,7 +66,7 @@ test.describe('Plugin Lifecycle', () => {
|
|||
});
|
||||
|
||||
test('test plugin navigation', async ({ page }) => {
|
||||
test.setTimeout(TEST_TIMEOUT_MS); // Increase timeout
|
||||
test.setTimeout(TEST_TIMEOUT_MS);
|
||||
|
||||
// Click on the plugin nav item to navigate to plugin
|
||||
const pluginNavItem = page.locator(API_TEST_PLUGIN_NAV_ITEM);
|
||||
|
|
@ -89,110 +90,13 @@ test.describe('Plugin Lifecycle', () => {
|
|||
await page.goto('/#/tag/TODAY');
|
||||
});
|
||||
|
||||
test('disable plugin and verify cleanup', async ({ page, workViewPage }) => {
|
||||
test.setTimeout(30000); // Increase timeout
|
||||
test('disable plugin and verify cleanup', async ({ page }) => {
|
||||
test.setTimeout(TEST_TIMEOUT_MS);
|
||||
|
||||
// Navigate to settings
|
||||
await page.click(SETTINGS_BTN);
|
||||
// Wait for settings page to be visible - use first() to avoid multiple matches
|
||||
await page
|
||||
.locator('.page-settings')
|
||||
.first()
|
||||
.waitFor({ state: 'visible', timeout: 10000 });
|
||||
|
||||
// Navigate to Plugins tab first
|
||||
await page.evaluate(() => {
|
||||
const pluginsTab = Array.from(
|
||||
document.querySelectorAll('mat-tab-header .mat-mdc-tab'),
|
||||
).find((tab) => {
|
||||
const icon = tab.querySelector('mat-icon');
|
||||
return icon?.textContent?.trim() === 'extension';
|
||||
});
|
||||
|
||||
if (pluginsTab) {
|
||||
(pluginsTab as HTMLElement).click();
|
||||
}
|
||||
});
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Expand plugin section
|
||||
await page.evaluate(() => {
|
||||
const pluginSection = document.querySelector('.plugin-section');
|
||||
if (pluginSection) {
|
||||
pluginSection.scrollIntoView({ behavior: 'instant', block: 'center' });
|
||||
}
|
||||
|
||||
const collapsible = document.querySelector('.plugin-section collapsible');
|
||||
if (collapsible && !collapsible.classList.contains('isExpanded')) {
|
||||
const header = collapsible.querySelector('.collapsible-header');
|
||||
if (header) {
|
||||
(header as HTMLElement).click();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Scroll plugin-management into view
|
||||
await page.evaluate(() => {
|
||||
const pluginMgmt = document.querySelector('plugin-management');
|
||||
if (pluginMgmt) {
|
||||
pluginMgmt.scrollIntoView({ behavior: 'instant', block: 'center' });
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for plugin management to be ready (attached, not necessarily visible)
|
||||
await page
|
||||
.locator('plugin-management')
|
||||
.waitFor({ state: 'attached', timeout: 10000 });
|
||||
// Wait for plugin cards to be available
|
||||
await page
|
||||
.locator('plugin-management mat-card')
|
||||
.first()
|
||||
.waitFor({ state: 'attached', timeout: 10000 });
|
||||
|
||||
// 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 || '';
|
||||
return title.includes(pluginName);
|
||||
});
|
||||
|
||||
if (targetCard) {
|
||||
const toggleButton = targetCard.querySelector(
|
||||
'mat-slide-toggle button[role="switch"]',
|
||||
) as HTMLButtonElement;
|
||||
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');
|
||||
|
||||
// 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 },
|
||||
);
|
||||
// Navigate to settings and set up plugin management view
|
||||
const initSuccess = await waitForPluginManagementInit(page);
|
||||
if (!initSuccess) {
|
||||
throw new Error('Plugin management failed to re-initialize for disable test');
|
||||
}
|
||||
|
||||
const disabled = await disablePluginWithVerification(
|
||||
|
|
@ -211,7 +115,6 @@ test.describe('Plugin Lifecycle', () => {
|
|||
const sideNavExists = (await page.locator(SIDENAV).count()) > 0;
|
||||
|
||||
if (sideNavExists) {
|
||||
// Check all plugin nav items to ensure API Test Plugin is not present
|
||||
const hasApiTestPlugin = await page.evaluate(() => {
|
||||
const menuItems = Array.from(
|
||||
document.querySelectorAll('magic-side-nav nav-item button'),
|
||||
|
|
@ -221,7 +124,6 @@ test.describe('Plugin Lifecycle', () => {
|
|||
|
||||
expect(hasApiTestPlugin).toBe(false);
|
||||
} else {
|
||||
// Magic-side-nav doesn't exist at all, which is unexpected
|
||||
expect(sideNavExists).toBe(true);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ test.describe('Work View Features', () => {
|
|||
workViewPage,
|
||||
testPrefix,
|
||||
}) => {
|
||||
test.setTimeout(30000);
|
||||
test.setTimeout(45000);
|
||||
|
||||
// Wait for work view to be ready
|
||||
await workViewPage.waitForTaskList();
|
||||
|
|
@ -116,11 +116,11 @@ test.describe('Work View Features', () => {
|
|||
|
||||
// Navigate to settings
|
||||
await page.goto('/#/config');
|
||||
await expect(page.locator('.page-settings')).toBeVisible();
|
||||
await expect(page.locator('.page-settings')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Navigate back
|
||||
await page.goto('/#/tag/TODAY');
|
||||
await expect(page.locator('task-list').first()).toBeVisible();
|
||||
await workViewPage.waitForTaskList();
|
||||
|
||||
// Verify task is still there
|
||||
await expect(page.locator(TASK_TITLE).first()).toContainText(/Persistent task/);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue