Initial commit

This commit is contained in:
2026-04-24 08:02:13 +02:00
commit 867c06295c
76 changed files with 10573 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
import { test, expect } from '@playwright/test';
test('simple connectivity test', async ({ page }) => {
await page.goto('http://localhost:5173/');
console.log('Page title:', await page.title());
expect(true).toBe(true);
});
+23
View File
@@ -0,0 +1,23 @@
import { test, expect } from '@playwright/test';
test('page loads without critical errors', async ({ page }) => {
const errors: string[] = [];
page.on('console', msg => {
if (msg.type() === 'error') {
errors.push(msg.text());
}
});
await page.goto('http://localhost:5173/');
await expect(page).toHaveTitle('Baby Sleep Tracker');
const awakeBanner = page.locator('text=Baby is awake');
const sleepingBanner = page.locator('text=Baby is sleeping');
const bannerVisible = await awakeBanner.or(sleepingBanner).isVisible();
expect(bannerVisible).toBe(true);
if (errors.length > 0) {
console.log('Console errors:', errors);
}
expect(errors.length).toBe(0);
});
+71
View File
@@ -0,0 +1,71 @@
import { test, expect } from '@playwright/test';
test.describe('Stats Tab', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:5173/stats');
await page.waitForTimeout(1000);
});
test('1. Stats tab is reachable via tab navigation', async ({ page }) => {
await page.goto('http://localhost:5173/');
await page.getByRole('link', { name: /Stats/i }).click();
await page.waitForTimeout(500);
await expect(page.getByRole('heading', { name: /Sleep Statistics/i })).toBeVisible();
});
test('2. Interval picker changes interval', async ({ page }) => {
await expect(page.getByRole('button', { name: /7d/i })).toBeVisible();
await page.getByRole('button', { name: /14d/i }).click();
await page.waitForTimeout(500);
await expect(page.getByRole('button', { name: /14d/i })).toBeVisible();
});
test('3. Timetable renders rows for selected interval', async ({ page }) => {
const timetable = page.locator('text=Sleep Timetable');
if (await timetable.isVisible({ timeout: 1000 }).catch(() => false)) {
await expect(page.locator('text=00:00').first()).toBeVisible();
}
});
test('4. Wakeup and bedtime plots render SVG lines', async ({ page }) => {
await page.getByRole('heading', { name: /Wakeup Times/i }).waitFor({ timeout: 2000 });
await expect(page.locator('h3:has-text("Wakeup Times")')).toBeVisible();
await page.locator('h3:has-text("Bedtime")').first().waitFor({ timeout: 2000 });
await expect(page.locator('h3:has-text("Bedtime")').first()).toBeVisible();
});
test('5. Settings popover for day/night boundary saves to localStorage and re-fetches', async ({ page }) => {
await page.getByRole('button', { name: /Settings/i }).click();
await page.waitForTimeout(300);
await expect(page.locator('text=Day starts')).toBeVisible();
await page.getByLabel('Day starts (HH:MM)').fill('08:00');
await page.getByRole('button', { name: /Save/i }).click();
await page.waitForTimeout(500);
const localStorage = await page.evaluate(() => {
return window.localStorage.getItem('baby-sleep-settings');
});
expect(localStorage).toContain('08:00');
});
test('6. Daily Summary shows chart with bars', async ({ page }) => {
await expect(page.locator('text=Daily Summary')).toBeVisible();
const chartBars = page.locator('.recharts-bar-rectangle');
if (await chartBars.first().isVisible({ timeout: 1000 }).catch(() => false)) {
await expect(chartBars.first()).toBeVisible();
}
});
test('7. Average summaries display below chart', async ({ page }) => {
const avgTotal = page.locator('text=Avg Total');
if (await avgTotal.isVisible({ timeout: 1000 }).catch(() => false)) {
await expect(avgTotal).toBeVisible();
}
});
});
+21
View File
@@ -0,0 +1,21 @@
import { test, expect } from '@playwright/test';
test('stop sleep gives 422', async ({ page }) => {
const errors: string[] = [];
page.on('response', res => {
if (res.url().includes('/api/sessions/') && res.status() >= 400) {
errors.push(`${res.status()} at ${res.url()}`);
}
});
await page.goto('http://localhost:5173/');
await page.getByRole('button', { name: /Start Sleep/i }).click();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: /Stop Sleep/i }).click();
await page.waitForTimeout(1000);
console.log('Errors:', errors);
expect(errors.length).toBe(0);
});
+94
View File
@@ -0,0 +1,94 @@
import { test, expect } from '@playwright/test';
test.describe('Tracker Flow', () => {
test.beforeEach(async ({ page }) => {
// 1. Navigate to the app
await page.goto('http://localhost:5173/');
// 2. Cleanup: If there is an active session, end it.
const stopBtn = page.getByRole('button', { name: /Stop Sleep/i });
if (await stopBtn.isVisible({ timeout: 1000 }).catch(() => false)) {
await stopBtn.click();
// Wait for the session to end and button to return to "Start Sleep" or be visible again.
await expect(page.getByRole('button', { name: /Start Sleep/i })).toBeVisible({ timeout: 5000 });
}
});
test('1. page loads — Start Sleep button is visible', async ({ page }) => {
// By default, there should be no active session (after cleanup), so "Baby is awake" should be visible.
await expect(page.getByRole('button', { name: /Start Sleep/i })).toBeVisible();
await expect(page.getByText(/Baby is awake/i)).toBeVisible();
});
test('2. click Start Sleep — button changes to Stop Sleep, status banner updates', async ({ page }) => {
const startBtn = page.getByRole('button', { name: /Start Sleep/i });
await startBtn.click();
// The button will temporarily show "Processing..." (disabled).
// We wait for it to transition to the active session state: "Stop Sleep".
await expect(page.getByRole('button', { name: /Stop Sleep/i })).toBeVisible();
await expect(page.getByText(/Baby is sleeping/i)).toBeVisible();
});
test('3. adjust start time to 5 minutes ago — verify API receives adjusted time', async ({ page }) => {
const datetimeInput = page.locator('input[type="datetime-local"]');
const now = new Date();
const fiveMinAgo = new Date(now.getTime() - 5 * 60 * 1000);
const adjustedTime = fiveMinAgo.toISOString().slice(0, 16);
await datetimeInput.fill(adjustedTime);
await page.getByRole('button', { name: /Start Sleep/i }).click();
// Wait for transition to "Stop Sleep"
await expect(page.getByRole('button', { name: /Stop Sleep/i })).toBeVisible();
await expect(page.getByText(/Baby is sleeping/i)).toBeVisible();
});
test('4. click Stop Sleep — button returns to Start Sleep', async ({ page }) => {
// 1. Start a session
await page.getByRole('button', { name: /Start Sleep/i }).click();
await expect(page.getByRole('button', { name: /Stop Sleep/i })).toBeVisible();
// 2. Stop the session
await page.getByRole('button', { name: /Stop Sleep/i }).click();
// After clicking, it should return to "Start Sleep"
await expect(page.getByRole('button', { name: /Start Sleep/i })).toBeVisible();
});
test('5. reload page — UI correctly reflects state from API', async ({ page }) => {
// 1. Start a session
await page.getByRole('button', { name: /Start Sleep/i }).click();
await expect(page.getByRole('button', { name: /Stop Sleep/i })).toBeVisible();
// 2. Reload the page
await page.reload();
// 3. Check if state is persistent from API
await expect(page.getByRole('button', { name: /Stop Sleep/i })).toBeVisible();
await expect(page.getByText(/Baby is sleeping/i)).toBeVisible();
});
test('6. Reset to now link resets the time field', async ({ page }) => {
const datetimeInput = page.locator('input[type="datetime-local"]');
// Fill with a dummy value to see if it resets
const futureTime = new Date(Date.now() + 10 * 60 * 1000).toISOString().slice(0, 16);
await datetimeInput.fill(futureTime);
// Click Reset
await page.getByRole('button', { name: /Reset to now/i }).click();
// Value should be updated (might slightly differ due to seconds but we check visibility/existence)
const newValue = await datetimeInput.inputValue();
expect(newValue).not.toBe(futureTime);
});
test('7. elapsed time shows for active session', async ({ page }) => {
await page.getByRole('button', { name: /Start Sleep/i }).click();
await expect(page.getByRole('button', { name: /Stop Sleep/i })).toBeVisible();
// Verify that elapsed time (e.g., "1m" or "just now") is visible
await expect(page.getByText(/(\d+h|\d+m|just\s*now)/i)).toBeVisible();
});
});