super-productivity/e2e/tests/focus-mode/flowtime-timer-bug-5117.spec.ts
2026-01-05 14:45:06 +01:00

291 lines
11 KiB
TypeScript

/**
* E2E test for GitHub issue #5117
* https://github.com/super-productivity/super-productivity/issues/5117
*
* Bug: Flowtime focus mode stops counting up at the value set in Countdown mode
* (e.g., 25 minutes or 5 minutes) instead of counting indefinitely.
*
* User reproduction steps:
* 1. Open focus mode
* 2. Switch to Countdown tab
* 3. Set countdown to 5 minutes (but do not start the session)
* 4. Switch to Flowtime tab
* 5. Start focus session counting up from 0
*
* Expected: Timer counts indefinitely
* Actual: Timer stops at 5 minutes (the Countdown value)
*/
import { test, expect } from '../../fixtures/test.fixture';
import { WorkViewPage } from '../../pages/work-view.page';
// Helper to parse time string "MM:SS" to seconds
const parseTime = (timeStr: string | null): number => {
if (!timeStr) return 0;
const trimmed = timeStr.trim();
const parts = trimmed.split(':');
if (parts.length !== 2) return 0;
const minutes = parseInt(parts[0], 10);
const seconds = parseInt(parts[1], 10);
const minutesInSeconds = minutes * 60;
return minutesInSeconds + seconds;
};
test.describe('Bug #5117: Flowtime timer stops at Countdown duration', () => {
test('Flowtime timer should count past the previously set Countdown duration', async ({
page,
testPrefix,
}) => {
const workViewPage = new WorkViewPage(page, testPrefix);
// Locators
const focusModeOverlay = page.locator('focus-mode-overlay');
const mainFocusButton = page
.getByRole('button')
.filter({ hasText: 'center_focus_strong' });
// Mode selector buttons - using the segmented button group
const flowtimeButton = page.locator('segmented-button-group button', {
hasText: 'Flowtime',
});
const countdownButton = page.locator('segmented-button-group button', {
hasText: 'Countdown',
});
// Duration slider (visible only in Countdown mode during preparation)
const durationSlider = page.locator('input-duration-slider');
// Play button to start the session (button element, not the icon inside)
const playButton = page.locator('focus-mode-main button.play-button');
// Clock display showing elapsed time
const clockTime = page.locator('focus-mode-main .clock-time');
// Wait for task list and add a task (required for focus mode)
await workViewPage.waitForTaskList();
await workViewPage.addTask('FlowTimeTestTask');
// Step 1: Open focus mode
await mainFocusButton.click();
await expect(focusModeOverlay).toBeVisible({ timeout: 5000 });
// Focus mode may show task selection placeholder first - select the task
const taskSelectionPlaceholder = page.locator('.task-title-placeholder');
const isPlaceholderVisible = await taskSelectionPlaceholder
.isVisible()
.catch(() => false);
if (isPlaceholderVisible) {
console.log('Task selection placeholder visible, selecting task...');
// Click on the placeholder to open task selector
await taskSelectionPlaceholder.click();
// Wait for task selector overlay
const taskSelectorOverlay = page.locator('.task-selector-overlay');
await expect(taskSelectorOverlay).toBeVisible({ timeout: 3000 });
// Click on the first suggested task (mat-option is in CDK overlay panel)
const suggestedTask = page.locator('mat-option, .mat-mdc-option').first();
await expect(suggestedTask).toBeVisible({ timeout: 5000 });
await suggestedTask.click();
// Wait for task selector overlay to close
await expect(taskSelectorOverlay).not.toBeVisible({ timeout: 5000 });
}
// Step 2: Switch to Countdown mode
await countdownButton.click();
await expect(countdownButton).toHaveClass(/is-active/, { timeout: 2000 });
// Step 3: The slider should be visible in Countdown mode during preparation
await expect(durationSlider).toBeVisible({ timeout: 3000 });
// Get initial timer state before any changes
const initialClockText = await clockTime.textContent();
console.log('Initial clock text in Countdown mode:', initialClockText);
// Step 4: Switch to Flowtime mode
await flowtimeButton.click();
await expect(flowtimeButton).toHaveClass(/is-active/, { timeout: 2000 });
// Duration slider should NOT be visible in Flowtime mode
await expect(durationSlider).not.toBeVisible({ timeout: 2000 });
// Verify clock shows 0:00 (Flowtime starts at 0 and counts up)
await expect(clockTime).toHaveText('0:00', { timeout: 3000 });
// Step 5: Start the focus session by clicking play button
await expect(playButton).toBeVisible({ timeout: 2000 });
await playButton.click();
// Wait for the 5-4-3-2-1 countdown animation to complete
const countdownComponent = page.locator('focus-mode-countdown');
// Wait for countdown to appear and then disappear
try {
await expect(countdownComponent).toBeVisible({ timeout: 2000 });
console.log('Countdown animation started...');
// Wait for countdown to complete (5 seconds + animation buffer)
await expect(countdownComponent).not.toBeVisible({ timeout: 15000 });
console.log('Countdown animation completed');
} catch {
console.log('Countdown animation not visible (may be skipped in settings)');
}
// Wait for the timer to start running
await page.waitForTimeout(2000);
// Wait for clock-time to show a non-zero value (indicating session has started)
await expect(async () => {
const text = await clockTime.textContent();
const trimmed = text?.trim() || '';
console.log('Current clock time:', trimmed);
const seconds = parseTime(trimmed);
expect(seconds).toBeGreaterThan(0);
}).toPass({ timeout: 10000 });
// Now verify the timer continues to increase
const time1 = await clockTime.textContent();
console.log('Time at T1:', time1);
await page.waitForTimeout(3000);
const time2 = await clockTime.textContent();
console.log('Time at T2:', time2);
const seconds1 = parseTime(time1);
const seconds2 = parseTime(time2);
console.log('Seconds at T1:', seconds1);
console.log('Seconds at T2:', seconds2);
// Timer should have increased
expect(seconds2).toBeGreaterThan(seconds1);
// Verify once more
await page.waitForTimeout(2000);
const time3 = await clockTime.textContent();
const seconds3 = parseTime(time3);
console.log('Seconds at T3:', seconds3);
expect(seconds3).toBeGreaterThan(seconds2);
});
test('Exact bug scenario: Set Countdown duration, switch to Flowtime, verify timer runs', async ({
page,
testPrefix,
}) => {
// This is the exact user scenario from bug report
const workViewPage = new WorkViewPage(page, testPrefix);
const focusModeOverlay = page.locator('focus-mode-overlay');
const mainFocusButton = page
.getByRole('button')
.filter({ hasText: 'center_focus_strong' });
const flowtimeButton = page.locator('segmented-button-group button', {
hasText: 'Flowtime',
});
const countdownButton = page.locator('segmented-button-group button', {
hasText: 'Countdown',
});
const playButton = page.locator('focus-mode-main button.play-button');
const clockTime = page.locator('focus-mode-main .clock-time');
const durationSlider = page.locator('input-duration-slider');
// Setup
await workViewPage.waitForTaskList();
await workViewPage.addTask('FlowTimeTestTask2');
// Open focus mode
await mainFocusButton.click();
await expect(focusModeOverlay).toBeVisible({ timeout: 5000 });
// Focus mode may show task selection placeholder first - select the task
const taskSelectionPlaceholder = page.locator('.task-title-placeholder');
const isPlaceholderVisible = await taskSelectionPlaceholder
.isVisible()
.catch(() => false);
if (isPlaceholderVisible) {
console.log('Task selection placeholder visible, selecting task...');
await taskSelectionPlaceholder.click();
const taskSelectorOverlay = page.locator('.task-selector-overlay');
await expect(taskSelectorOverlay).toBeVisible({ timeout: 3000 });
const suggestedTask = page.locator('mat-option, .mat-mdc-option').first();
await expect(suggestedTask).toBeVisible({ timeout: 5000 });
await suggestedTask.click();
// Wait for task selector overlay to close
await expect(taskSelectorOverlay).not.toBeVisible({ timeout: 5000 });
}
// Step 1: Switch to Countdown mode
await countdownButton.click();
await expect(countdownButton).toHaveClass(/is-active/, { timeout: 2000 });
await expect(durationSlider).toBeVisible({ timeout: 2000 });
// Step 2: Note the countdown duration displayed (default 25 min or whatever is set)
const countdownTime = await clockTime.textContent();
console.log('Countdown duration displayed:', countdownTime);
// Step 3: Switch to Flowtime (without starting the countdown session)
await flowtimeButton.click();
await expect(flowtimeButton).toHaveClass(/is-active/, { timeout: 2000 });
// Clock should show 0:00 for Flowtime
await expect(clockTime).toHaveText('0:00', { timeout: 3000 });
// Step 4: Start the Flowtime session
await expect(playButton).toBeVisible({ timeout: 2000 });
await playButton.click();
// Wait for countdown animation to complete
const countdownComponent = page.locator('focus-mode-countdown');
try {
await expect(countdownComponent).toBeVisible({ timeout: 2000 });
console.log('Countdown animation started...');
await expect(countdownComponent).not.toBeVisible({ timeout: 15000 });
console.log('Countdown animation completed');
} catch {
console.log('Countdown animation not visible (may be skipped)');
}
// Wait for session to start
await page.waitForTimeout(2000);
// Wait for clock-time to show a non-zero value
await expect(async () => {
const text = await clockTime.textContent();
const trimmed = text?.trim() || '';
console.log('Current clock time:', trimmed);
const seconds = parseTime(trimmed);
expect(seconds).toBeGreaterThan(0);
}).toPass({ timeout: 10000 });
// Step 5: Verify timer is counting up and doesn't stop
const time1 = await clockTime.textContent();
console.log('Flowtime time T1:', time1);
await page.waitForTimeout(3000);
const time2 = await clockTime.textContent();
console.log('Flowtime time T2:', time2);
await page.waitForTimeout(3000);
const time3 = await clockTime.textContent();
console.log('Flowtime time T3:', time3);
// Parse times
const seconds1 = parseTime(time1);
const seconds2 = parseTime(time2);
const seconds3 = parseTime(time3);
console.log('Seconds:', seconds1, '->', seconds2, '->', seconds3);
// All times should be increasing (timer is running)
expect(seconds2).toBeGreaterThan(seconds1);
expect(seconds3).toBeGreaterThan(seconds2);
// The timer should continue running indefinitely
// If bug is present, it would stop at the countdown duration
});
});