import { test, expect } from '@playwright/test'; test.describe('PNG Export', () => { test('renders month and exports PNG within size/time budget', async ({ page, browserName }) => { await page.goto('/'); await expect(page.getByRole('heading', { level: 1 })).toHaveText(/GlowTrack/i); // Step 1: Ensure we have a month view rendered with tiles const gridContainer = page.locator('[data-testid="wellbeing-grid"]'); await expect(gridContainer).toBeVisible(); // Ensure at least 28-31 tiles are visible (month view) const tiles = page.locator('[data-testid="day-tile"]'); const tileCount = await tiles.count(); expect(tileCount).toBeGreaterThanOrEqual(28); // At least a month's worth // Step 2: Add some data to a few tiles to make the export meaningful // This creates visual content that should be captured in PNG await tiles.first().click(); // Set mood if mood controls are available const hueInput = page.locator('[data-testid="mood-hue-slider"]'); if (await hueInput.isVisible()) { await hueInput.fill('180'); // Blue mood } // Add positive habit if controls are available const addPositive = page.locator('[data-testid="add-positive-habit"]'); if (await addPositive.isVisible()) { await addPositive.click(); } // Close any editor modal/overlay const closeButton = page.locator('[data-testid="close-day-editor"]'); if (await closeButton.isVisible()) { await closeButton.click(); } else { // Try clicking outside to close await gridContainer.click({ position: { x: 10, y: 10 } }); } // Step 3: Wait for any visual updates to complete await page.waitForTimeout(500); // Step 4: Trigger PNG export const exportButton = page.locator('[data-testid="export-png-button"]'); // Start timing the export operation const startTime = Date.now(); // Handle the download that should be triggered by PNG export const downloadPromise = page.waitForEvent('download', { timeout: 10000 }); if (await exportButton.isVisible()) { await exportButton.click(); // Wait for download to complete const download = await downloadPromise; const endTime = Date.now(); const exportDuration = endTime - startTime; // Step 5: Validate the PNG export meets budgets // Time budget: Export should complete within 5 seconds for a month view expect(exportDuration).toBeLessThan(5000); // Size budget: Get the download and check file size const path = await download.path(); if (path) { const fs = await import('fs'); const stats = fs.statSync(path); // Size budget: PNG should be reasonable size (not too small, not too large) // Minimum: 1KB (should have actual content) // Maximum: 5MB (should be reasonable for a month grid) expect(stats.size).toBeGreaterThan(1024); // > 1KB expect(stats.size).toBeLessThan(5 * 1024 * 1024); // < 5MB // Verify it's actually a PNG file by checking magic bytes const buffer = fs.readFileSync(path); const pngSignature = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]); expect(buffer.subarray(0, 8)).toEqual(pngSignature); // Suggested filename should contain date/timestamp const suggestedFilename = download.suggestedFilename(); expect(suggestedFilename).toMatch(/\.png$/i); expect(suggestedFilename).toMatch(/glowtrack|grid|export/i); } } else { // If export button not yet implemented, we expect this test to fail // This aligns with TDD approach - test should fail until implementation exists throw new Error('PNG export button not found - export functionality not yet implemented'); } }); test('PNG export handles canvas rendering correctly', async ({ page }) => { await page.goto('/'); // This test focuses on the canvas/toBlob functionality specifically const gridContainer = page.locator('[data-testid="wellbeing-grid"]'); await expect(gridContainer).toBeVisible(); // Check if canvas element is present (renderer should use Canvas for tiles) const canvas = page.locator('canvas'); if (await canvas.count() > 0) { // Verify canvas has reasonable dimensions for a month grid const canvasElement = canvas.first(); const boundingBox = await canvasElement.boundingBox(); if (boundingBox) { expect(boundingBox.width).toBeGreaterThan(200); // Reasonable minimum width expect(boundingBox.height).toBeGreaterThan(100); // Reasonable minimum height // Verify canvas has actual content (not blank) // This is a proxy test - actual implementation would use toBlob const canvasData = await page.evaluate(() => { const canvas = document.querySelector('canvas') as HTMLCanvasElement; if (!canvas) return null; const ctx = canvas.getContext('2d'); if (!ctx) return null; const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Check if canvas has any non-transparent pixels let hasContent = false; for (let i = 3; i < data.length; i += 4) { // Check alpha channel if (data[i] > 0) { hasContent = true; break; } } return { width: canvas.width, height: canvas.height, hasContent }; }); if (canvasData) { expect(canvasData.hasContent).toBe(true); } } } else { // Canvas not yet implemented - this is expected in TDD approach console.log('Canvas element not found - renderer not yet implemented'); } }); test('PNG export respects screen resolution and quality settings', async ({ page }) => { await page.goto('/'); const gridContainer = page.locator('[data-testid="wellbeing-grid"]'); await expect(gridContainer).toBeVisible(); // Test different export quality settings if available const qualitySelector = page.locator('[data-testid="export-quality-selector"]'); const exportButton = page.locator('[data-testid="export-png-button"]'); if (await qualitySelector.isVisible() && await exportButton.isVisible()) { // Test high quality export await qualitySelector.selectOption('high'); const downloadPromise = page.waitForEvent('download', { timeout: 10000 }); await exportButton.click(); const download = await downloadPromise; const path = await download.path(); if (path) { const fs = await import('fs'); const stats = fs.statSync(path); // High quality should produce larger files expect(stats.size).toBeGreaterThan(2048); // > 2KB for high quality } } else { // Export quality controls not yet implemented console.log('Export quality controls not found - advanced export options not yet implemented'); } }); });