import { test, expect } from '@playwright/test'; test.describe('Nur Falah — Muslim Companion', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); // Wait for app to load - new branding uses "Nur" and "Falah" await page.waitForSelector('.brand-name:has-text("Nur")'); }); test('should load and display app title', async ({ page }) => { await expect(page.locator('.brand-name')).toHaveText('Nur'); await expect(page.locator('.brand-accent')).toHaveText('Falah'); await expect(page.locator('.brand-tag')).toHaveText('Muslim Companion'); }); test('should have 6 navigation tabs', async ({ page }) => { const tabs = page.locator('nav .tab'); await expect(tabs).toHaveCount(6); const tabLabels = await tabs.locator('.tab-label').allTextContents(); expect(tabLabels).toEqual(['Prayer', 'Qibla', 'Quran', 'Hijri', 'Tasbih', '99 Names']); }); test('should navigate between tabs using keyboard', async ({ page }) => { // Start at Prayer tab (index 0) await expect(page.locator('nav .tab.active .tab-label')).toHaveText('Prayer'); // Press ArrowRight to go to Qibla await page.keyboard.press('ArrowRight'); await expect(page.locator('nav .tab.active .tab-label')).toHaveText('Qibla'); // Press ArrowRight to go to Quran await page.keyboard.press('ArrowRight'); await expect(page.locator('nav .tab.active .tab-label')).toHaveText('Quran'); // Press ArrowLeft to go back to Qibla await page.keyboard.press('ArrowLeft'); await expect(page.locator('nav .tab.active .tab-label')).toHaveText('Qibla'); }); test('should navigate between tabs by clicking', async ({ page }) => { // Click on Tasbih tab await page.click('nav .tab:has-text("Tasbih")'); await expect(page.locator('nav .tab.active .tab-label')).toHaveText('Tasbih'); // Click on 99 Names tab await page.click('nav .tab:has-text("99 Names")'); await expect(page.locator('nav .tab.active .tab-label')).toHaveText('99 Names'); }); test.describe('Prayer Times tab', () => { test('should show loading state initially', async ({ page }) => { await page.click('nav .tab:has-text("Prayer")'); await expect(page.locator('.card h2')).toHaveText('🕌 Prayer Times'); }); test('should display prayer times after load', async ({ page }) => { await page.click('nav .tab:has-text("Prayer")'); await page.locator('.prayer-grid, .next-prayer').first().waitFor({ timeout: 15000 }); const prayerGrid = page.locator('.prayer-grid'); if (await prayerGrid.isVisible()) { await expect(prayerGrid.locator('.prayer-row')).toHaveCount(6); } }); test('should have calculation method selector', async ({ page }) => { await page.click('nav .tab:has-text("Prayer")'); await page.locator('.method-select select').waitFor({ timeout: 10000 }); const select = page.locator('.method-select select'); await expect(select).toBeVisible(); const options = await select.locator('option').allTextContents(); expect(options).toContain('Muslim World League'); expect(options).toContain('ISNA (N. America)'); }); }); test.describe('Qibla tab', () => { test('should display compass', async ({ page }) => { await page.click('nav .tab:has-text("Qibla")'); await expect(page.locator('.card h2')).toHaveText('🧭 Qibla Finder'); await Promise.race([ page.locator('.compass').waitFor({ timeout: 15000 }).catch(() => {}), page.locator('text=Compass permission denied').waitFor({ timeout: 15000 }).catch(() => {}), page.locator('text=Geolocation not supported').waitFor({ timeout: 15000 }).catch(() => {}) ]); const compass = page.locator('.compass'); if (await compass.isVisible()) { await expect(compass).toBeVisible(); await expect(page.locator('.bearing-num')).toBeVisible(); } }); }); test.describe('Quran tab', () => { test('should display surah list', async ({ page }) => { await page.click('nav .tab:has-text("Quran")'); await expect(page.locator('.card h2')).toHaveText('📖 Holy Quran'); await Promise.race([ page.locator('.surah-btn').first().waitFor({ timeout: 15000 }).catch(() => {}), page.locator('text=Network error').waitFor({ timeout: 15000 }).catch(() => {}) ]); const surahBtns = page.locator('.surah-btn'); if (await surahBtns.first().isVisible()) { await expect(surahBtns).toHaveCount(114); } }); test('should navigate to surah detail on click', async ({ page }) => { await page.click('nav .tab:has-text("Quran")'); await page.locator('.surah-btn').first().waitFor({ timeout: 15000 }); await page.click('.surah-btn:first-child'); await Promise.race([ page.locator('.ayah').first().waitFor({ timeout: 10000 }).catch(() => {}), page.locator('text=Failed to load surah').waitFor({ timeout: 10000 }).catch(() => {}) ]); const ayahs = page.locator('.ayah'); if (await ayahs.first().isVisible()) { await expect(ayahs).toHaveCount(7); } await expect(page.locator('.back-btn')).toHaveText('← Surahs'); }); test('should have Arabic/English toggle', async ({ page }) => { await page.click('nav .tab:has-text("Quran")'); await page.locator('.surah-btn').first().waitFor({ timeout: 15000 }); await page.click('.surah-btn:first-child'); await page.locator('.view-toggles').waitFor({ timeout: 10000 }); await expect(page.locator('.view-toggles')).toBeVisible(); await expect(page.locator('.view-toggles label:has-text("Arabic")')).toBeVisible(); await expect(page.locator('.view-toggles label:has-text("English")')).toBeVisible(); }); test('should have audio play buttons for ayahs', async ({ page }) => { await page.click('nav .tab:has-text("Quran")'); await page.locator('.surah-btn').first().waitFor({ timeout: 15000 }); await page.click('.surah-btn:first-child'); await page.locator('.audio-btn').first().waitFor({ timeout: 10000 }); const audioBtns = page.locator('.audio-btn'); await expect(audioBtns.first()).toBeVisible(); }); }); test.describe('Hijri tab', () => { test('should display hijri date', async ({ page }) => { await page.click('nav .tab:has-text("Hijri")'); await expect(page.locator('.card h2')).toHaveText('📅 Hijri Calendar'); await Promise.race([ page.locator('.hijri-day').waitFor({ timeout: 10000 }).catch(() => {}), page.locator('text=Network error').waitFor({ timeout: 10000 }).catch(() => {}) ]); const hijriDay = page.locator('.hijri-day'); if (await hijriDay.isVisible()) { await expect(hijriDay).toBeVisible(); await expect(page.locator('.hijri-month')).toBeVisible(); await expect(page.locator('.hijri-year')).toBeVisible(); } }); }); test.describe('Tasbih tab', () => { test('should display counter', async ({ page }) => { await page.click('nav .tab:has-text("Tasbih")'); await expect(page.locator('.card h2')).toHaveText('📿 Tasbih Counter'); await expect(page.locator('.counter-ring')).toBeVisible(); await expect(page.locator('.counter-ring text').first()).toBeVisible(); }); test('should increment counter on tap', async ({ page }) => { await page.click('nav .tab:has-text("Tasbih")'); const counterRing = page.locator('.counter-ring'); await counterRing.click(); await expect(page.locator('.counter-ring text').first()).toHaveText('1'); }); test('should have target selector', async ({ page }) => { await page.click('nav .tab:has-text("Tasbih")'); const select = page.locator('#bead-select'); await expect(select).toBeVisible(); const options = await select.locator('option').allTextContents(); expect(options).toEqual(['33', '99', '100', '500']); }); test('should reset counter', async ({ page }) => { await page.click('nav .tab:has-text("Tasbih")'); await page.click('.counter-ring'); await page.click('.counter-ring'); await page.click('.counter-ring'); await expect(page.locator('.counter-ring text').first()).toHaveText('3'); await page.click('button.secondary:has-text("Reset")'); await expect(page.locator('.counter-ring text').first()).toHaveText('0'); }); }); test.describe('99 Names tab', () => { test('should display all 99 names', async ({ page }) => { await page.click('nav .tab:has-text("99 Names")'); await expect(page.locator('.card h2')).toHaveText('💚 99 Names of Allah'); await page.locator('.name-card').first().waitFor({ timeout: 10000 }); const nameCards = page.locator('.name-card'); await expect(nameCards).toHaveCount(99); }); test('should expand name on click to show meaning', async ({ page }) => { await page.click('nav .tab:has-text("99 Names")'); await page.locator('.name-card').first().waitFor({ timeout: 10000 }); await page.click('.name-card:first-child'); await expect(page.locator('.name-card.expanded .name-meaning')).toBeVisible(); await expect(page.locator('.name-card.expanded .name-meaning')).toHaveText('The Most Merciful'); }); test('should filter names by search', async ({ page }) => { await page.click('nav .tab:has-text("99 Names")'); await page.locator('.search-input').waitFor({ timeout: 10000 }); await page.fill('.search-input', 'Rahman'); await page.waitForTimeout(500); const visibleNames = page.locator('.name-card'); const count = await visibleNames.evaluateAll(cards => cards.filter(c => c.offsetParent !== null).length ); expect(count).toBeGreaterThanOrEqual(1); await page.fill('.search-input', ''); await expect(page.locator('.name-card')).toHaveCount(99); }); test('should filter by meaning', async ({ page }) => { await page.click('nav .tab:has-text("99 Names")'); await page.locator('.search-input').waitFor({ timeout: 10000 }); await page.fill('.search-input', 'King'); await page.waitForTimeout(500); const visibleNames = page.locator('.name-card'); const count = await visibleNames.evaluateAll(cards => cards.filter(c => c.offsetParent !== null).length ); expect(count).toBeGreaterThanOrEqual(1); }); }); test.describe('PWA features', () => { test('should have manifest link', async ({ page }) => { const manifest = page.locator('link[rel="manifest"]').first(); await expect(manifest).toHaveAttribute('href', '/manifest.webmanifest'); }); test('should have theme color meta', async ({ page }) => { const themeColor = page.locator('meta[name="theme-color"]'); await expect(themeColor).toHaveAttribute('content', '#070A0D'); }); test('should have apple PWA meta tags', async ({ page }) => { await expect(page.locator('meta[name="apple-mobile-web-app-capable"]')).toHaveAttribute('content', 'yes'); await expect(page.locator('meta[name="apple-mobile-web-app-title"]')).toHaveAttribute('content', 'Nur Falah'); }); }); });