mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-22 18:30:09 +00:00
fix(e2e): improve supersync test stability and build compatibility
E2E test improvements: - Increase timeouts for sync button and add task bar visibility checks - Add retry logic for sync button wait in setupSuperSync - Handle dialog close race conditions in save button click - Fix simple counter test to work with collapsible sections and inline forms Build fixes: - Add es2022 lib/target and baseUrl to electron tsconfig - Include window-ea.d.ts for proper type resolution - Add @ts-ignore for import.meta.url in reminder service for Electron build
This commit is contained in:
parent
d54156dda3
commit
ab0371fac6
5 changed files with 106 additions and 30 deletions
|
|
@ -27,18 +27,22 @@ export abstract class BasePage {
|
|||
|
||||
const inputEl = this.page.locator('add-task-bar.global input');
|
||||
|
||||
// If the global input is not present, open the Add Task Bar first
|
||||
const inputCount = await inputEl.count();
|
||||
if (inputCount === 0) {
|
||||
// Check if input is visible - if not, try clicking the add button
|
||||
const isInputVisible = await inputEl
|
||||
.first()
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
if (!isInputVisible) {
|
||||
const addBtn = this.page.locator('.tour-addBtn');
|
||||
await addBtn.waitFor({ state: 'visible', timeout: 10000 });
|
||||
// Wait for add button with longer timeout - it depends on config loading
|
||||
await addBtn.waitFor({ state: 'visible', timeout: 20000 });
|
||||
await addBtn.click();
|
||||
// Wait for input to appear after clicking
|
||||
await this.page.waitForTimeout(300);
|
||||
await this.page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
// Ensure input is visible and interactable
|
||||
await inputEl.first().waitFor({ state: 'visible', timeout: 15000 });
|
||||
// Ensure input is visible and interactable with longer timeout
|
||||
await inputEl.first().waitFor({ state: 'visible', timeout: 20000 });
|
||||
await inputEl.first().waitFor({ state: 'attached', timeout: 5000 });
|
||||
|
||||
// Wait for Angular to stabilize before interacting
|
||||
|
|
|
|||
|
|
@ -67,7 +67,18 @@ export class SuperSyncPage extends BasePage {
|
|||
*/
|
||||
async setupSuperSync(config: SuperSyncConfig): Promise<void> {
|
||||
// Wait for sync button to be ready first
|
||||
await this.syncBtn.waitFor({ state: 'visible', timeout: 10000 });
|
||||
// The sync button depends on globalConfig being loaded (isSyncIconEnabled),
|
||||
// which can take time after initial app load. Use longer timeout and retry.
|
||||
const syncBtnTimeout = 30000;
|
||||
try {
|
||||
await this.syncBtn.waitFor({ state: 'visible', timeout: syncBtnTimeout });
|
||||
} catch {
|
||||
// If sync button not visible, the app might not be fully loaded
|
||||
// Wait a bit more and try once more
|
||||
console.log('[SuperSyncPage] Sync button not found initially, waiting longer...');
|
||||
await this.page.waitForTimeout(2000);
|
||||
await this.syncBtn.waitFor({ state: 'visible', timeout: syncBtnTimeout });
|
||||
}
|
||||
|
||||
// Use right-click to always open sync settings dialog
|
||||
// (left-click triggers sync if already configured)
|
||||
|
|
@ -157,8 +168,40 @@ export class SuperSyncPage extends BasePage {
|
|||
}
|
||||
}
|
||||
|
||||
// Save
|
||||
await this.saveBtn.click();
|
||||
// Save - use a robust click that handles element detachment during dialog close
|
||||
// The dialog may close and navigation may start before click completes
|
||||
try {
|
||||
// Wait for button to be stable before clicking
|
||||
await this.saveBtn.waitFor({ state: 'visible', timeout: 5000 });
|
||||
await this.page.waitForTimeout(100); // Brief settle
|
||||
|
||||
// Click and don't wait for navigation to complete - just initiate the action
|
||||
await Promise.race([
|
||||
this.saveBtn.click({ timeout: 5000 }),
|
||||
// If dialog closes quickly, the click may fail - that's OK if dialog is gone
|
||||
this.page
|
||||
.locator('mat-dialog-container')
|
||||
.waitFor({ state: 'detached', timeout: 5000 }),
|
||||
]);
|
||||
} catch (e) {
|
||||
// If click failed but dialog is already closed, that's fine
|
||||
const dialogStillOpen = await this.page
|
||||
.locator('mat-dialog-container')
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
if (dialogStillOpen) {
|
||||
// Dialog still open - the click actually failed
|
||||
throw e;
|
||||
}
|
||||
// Dialog closed - click worked or was unnecessary
|
||||
console.log('[SuperSyncPage] Dialog closed (click may have been interrupted)');
|
||||
}
|
||||
|
||||
// Wait for dialog to fully close
|
||||
await this.page
|
||||
.locator('mat-dialog-container')
|
||||
.waitFor({ state: 'detached', timeout: 5000 })
|
||||
.catch(() => {});
|
||||
|
||||
// Check if sync starts automatically (it should if enabled)
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -48,32 +48,51 @@ base.describe('@supersync Simple Counter Sync', () => {
|
|||
);
|
||||
await settingsBtn.waitFor({ state: 'visible', timeout: 15000 });
|
||||
await settingsBtn.click();
|
||||
await client.page.waitForURL(/settings/);
|
||||
await client.page.waitForURL(/config/);
|
||||
await client.page.waitForTimeout(500);
|
||||
|
||||
// Click on Simple Counters section
|
||||
// Click on Simple Counters section (it's inside a collapsible component)
|
||||
// The translated title is "Simple Counters & Habit Tracking"
|
||||
// It's under "Productivity Helper" section, may need to scroll to see it
|
||||
const simpleCountersSection = client.page.locator(
|
||||
'section-header:has-text("Simple Counters")',
|
||||
'.collapsible-header:has-text("Simple Counter")',
|
||||
);
|
||||
|
||||
// Scroll to section and wait for it
|
||||
await simpleCountersSection.scrollIntoViewIfNeeded();
|
||||
await simpleCountersSection.waitFor({ state: 'visible', timeout: 10000 });
|
||||
await simpleCountersSection.click();
|
||||
await client.page.waitForTimeout(300);
|
||||
|
||||
// Click Add Counter button
|
||||
const addBtn = client.page.locator('button:has-text("Add Counter")');
|
||||
// Wait for collapsible to expand
|
||||
await client.page.waitForTimeout(500);
|
||||
|
||||
// Click Add Counter button - text is "Add simple counter/ habit"
|
||||
// This is a formly repeat type that adds fields inline (not a dialog)
|
||||
// The repeat section type has a footer with the add button
|
||||
const addBtn = client.page.locator(
|
||||
'repeat-section-type .footer button, button:has-text("Add simple counter")',
|
||||
);
|
||||
await addBtn.scrollIntoViewIfNeeded();
|
||||
await addBtn.waitFor({ state: 'visible', timeout: 5000 });
|
||||
await addBtn.click();
|
||||
|
||||
// Wait for dialog
|
||||
const dialog = client.page.locator('dialog-simple-counter-edit');
|
||||
await expect(dialog).toBeVisible({ timeout: 5000 });
|
||||
// Wait for inline form fields to appear
|
||||
await client.page.waitForTimeout(500);
|
||||
|
||||
// Fill title
|
||||
const titleInput = dialog.locator('input[formcontrolname="title"]');
|
||||
// Find the newly added counter row (last one in the list)
|
||||
// The repeat section type creates .row elements inside .list-wrapper
|
||||
const counterRows = client.page.locator('repeat-section-type .row');
|
||||
const lastCounterRow = counterRows.last();
|
||||
|
||||
// Fill title - find the title input in the last counter row
|
||||
const titleInput = lastCounterRow.locator('input').first();
|
||||
await titleInput.scrollIntoViewIfNeeded();
|
||||
await titleInput.waitFor({ state: 'visible', timeout: 5000 });
|
||||
await titleInput.fill(title);
|
||||
|
||||
// Select type
|
||||
const typeSelect = dialog.locator('mat-select[formcontrolname="type"]');
|
||||
// Select type - find the select in the last counter row
|
||||
const typeSelect = lastCounterRow.locator('mat-select').first();
|
||||
await typeSelect.scrollIntoViewIfNeeded();
|
||||
await typeSelect.click();
|
||||
await client.page.waitForTimeout(300);
|
||||
const typeOption = client.page.locator(
|
||||
|
|
@ -81,12 +100,18 @@ base.describe('@supersync Simple Counter Sync', () => {
|
|||
);
|
||||
await typeOption.click();
|
||||
|
||||
// Save
|
||||
const saveBtn = dialog.locator('button:has-text("Save")');
|
||||
// Wait for dropdown to close
|
||||
await client.page.waitForTimeout(300);
|
||||
|
||||
// Save the form - the simple counter cfg has a Save button
|
||||
const saveBtn = client.page.locator(
|
||||
'simple-counter-cfg button:has-text("Save"), .submit-button:has-text("Save")',
|
||||
);
|
||||
await saveBtn.scrollIntoViewIfNeeded();
|
||||
await saveBtn.click();
|
||||
|
||||
// Wait for dialog to close
|
||||
await expect(dialog).not.toBeVisible({ timeout: 5000 });
|
||||
// Wait for save to complete
|
||||
await client.page.waitForTimeout(500);
|
||||
|
||||
// Navigate back to work view using home button or similar
|
||||
await client.page.goto('/#/tag/TODAY/tasks');
|
||||
|
|
|
|||
|
|
@ -10,9 +10,12 @@
|
|||
"skipLibCheck": true,
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
"downlevelIteration": true,
|
||||
"lib": ["dom"],
|
||||
"esModuleInterop": true
|
||||
"lib": ["dom", "es2022"],
|
||||
"target": "es2022",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"baseUrl": ".."
|
||||
},
|
||||
"include": ["main.ts", "**/*.ts"],
|
||||
"include": ["main.ts", "**/*.ts", "../src/app/core/window-ea.d.ts"],
|
||||
"exclude": ["../node_modules", "**/*.spec.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ export class ReminderService {
|
|||
throw new Error('No service workers supported :(');
|
||||
}
|
||||
|
||||
// @ts-ignore - import.meta.url works in browser ES modules; ignore for electron CommonJS build
|
||||
this._w = new Worker(new URL('./reminder.worker', import.meta.url), {
|
||||
name: 'reminder',
|
||||
type: 'module',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue