Add sales automation stack: strategy docs, MCP scripts, PWA refinements
- SALES_STRATEGY.md: $1K/day plan (37 orders @ $27.50 AOV, 1,700 visits) - TRIPWIRE_FUNNEL.md: 7-email Mautic sequence + lead scoring - MONETIZATION.md: 9 monetization options for Nūr PWA (no ads, no registration) - EXECUTABLE_PLAN.md: MCP-executable automation plan - DAILY_OPS.md: daily KPI checklist (visits, orders, sends, recoveries) - scripts/: automation.py, mautic_setup.py, gumroad_setup.py, scout_scrape.py, analytics.py, crontab - quality_leads.json/.csv: 12 enriched Islamic-tech leads for Mautic import - content_queue.json: 10 SEO blog posts for Ghost - PWA: icons, sw.js, e2e + vitest coverage, generator scripts
This commit is contained in:
+294
@@ -0,0 +1,294 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,218 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Nur Falah — Human-like UAT Journey', () => {
|
||||
test.setTimeout(120000); // 2 minutes for full journey
|
||||
|
||||
test('Complete user journey: Prayer → Quran → Tasbih → Names', async ({ page }) => {
|
||||
// --- ARRIVE ---
|
||||
await page.goto('/');
|
||||
await page.waitForSelector('.brand-name:has-text("Nur")');
|
||||
await page.waitForTimeout(1000); // Human pause to orient
|
||||
|
||||
// Verify brand
|
||||
await expect(page.locator('.brand-name')).toHaveText('Nur');
|
||||
await expect(page.locator('.brand-accent')).toHaveText('Falah');
|
||||
console.log('✅ Landed on Nur Falah home');
|
||||
|
||||
// --- PRAYER TIMES TAB ---
|
||||
await page.hover('nav .tab:has-text("Prayer")');
|
||||
await page.waitForTimeout(300);
|
||||
await page.click('nav .tab:has-text("Prayer")');
|
||||
await page.waitForTimeout(800); // Wait for API
|
||||
|
||||
await expect(page.locator('.card h2')).toHaveText('🕌 Prayer Times');
|
||||
await page.waitForSelector('.prayer-row', { timeout: 15000 });
|
||||
|
||||
const prayerRows = page.locator('.prayer-row');
|
||||
await expect(prayerRows).toHaveCount(6);
|
||||
console.log('✅ Prayer times loaded (6 prayers)');
|
||||
|
||||
// Scroll through prayer times
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await page.hover(`.prayer-row:nth-child(${i + 1})`);
|
||||
await page.waitForTimeout(400);
|
||||
}
|
||||
|
||||
// Change calculation method
|
||||
await page.hover('.method-select select');
|
||||
await page.waitForTimeout(200);
|
||||
await page.selectOption('.method-select select', '4'); // Umm al-Qura
|
||||
await page.waitForTimeout(1000); // Wait for refetch
|
||||
console.log('✅ Changed calculation method to Umm al-Qura');
|
||||
|
||||
// Enable notifications
|
||||
const notifyToggle = page.locator('.toggle-label input');
|
||||
if (await notifyToggle.isEnabled()) {
|
||||
await notifyToggle.check();
|
||||
await page.waitForTimeout(500);
|
||||
console.log('✅ Enabled prayer notifications');
|
||||
}
|
||||
|
||||
// --- QIBLA TAB ---
|
||||
await page.hover('nav .tab:has-text("Qibla")');
|
||||
await page.waitForTimeout(300);
|
||||
await page.click('nav .tab:has-text("Qibla")');
|
||||
await page.waitForTimeout(1500); // Compass init
|
||||
|
||||
await expect(page.locator('.card h2')).toHaveText('🧭 Qibla Finder');
|
||||
await page.waitForSelector('.compass', { timeout: 15000 });
|
||||
|
||||
// Watch compass needle
|
||||
await page.hover('.compass');
|
||||
await page.waitForTimeout(2000);
|
||||
console.log('✅ Qibla compass displayed');
|
||||
|
||||
// --- QURAN TAB ---
|
||||
await page.hover('nav .tab:has-text("Quran")');
|
||||
await page.waitForTimeout(300);
|
||||
await page.click('nav .tab:has-text("Quran")');
|
||||
await page.waitForTimeout(2000); // Surah list load
|
||||
|
||||
await expect(page.locator('.card h2')).toHaveText('📖 Holy Quran');
|
||||
await page.waitForSelector('.surah-btn', { timeout: 15000 });
|
||||
|
||||
const surahCount = await page.locator('.surah-btn').count();
|
||||
expect(surahCount).toBe(114);
|
||||
console.log(`✅ Surah list loaded (${surahCount} surahs)`);
|
||||
|
||||
// Scroll through first 5 surahs
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await page.hover(`.surah-btn:nth-child(${i + 1})`);
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
|
||||
// Open Al-Fatiha
|
||||
await page.click('.surah-btn:first-child');
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
await expect(page.locator('.back-btn')).toHaveText('← Surahs');
|
||||
await expect(page.locator('.surah-title-block h3')).toHaveText('Al-Faatiha');
|
||||
console.log('✅ Opened Al-Fatiha (Surah 1)');
|
||||
|
||||
// Toggle Arabic/English
|
||||
await page.hover('.view-toggles label:has-text("Arabic")');
|
||||
await page.waitForTimeout(200);
|
||||
await page.uncheck('.view-toggles input[type="checkbox"]:nth-child(1)'); // Uncheck Arabic
|
||||
await page.waitForTimeout(300);
|
||||
await page.check('.view-toggles input[type="checkbox"]:nth-child(1)'); // Re-check Arabic
|
||||
await page.waitForTimeout(300);
|
||||
console.log('✅ Toggled Arabic/English view');
|
||||
|
||||
// Play first ayah audio
|
||||
await page.waitForSelector('.audio-btn', { timeout: 10000 }).catch(() => {});
|
||||
const audioBtn = page.locator('.audio-btn').first();
|
||||
if (await audioBtn.isVisible()) {
|
||||
await audioBtn.hover();
|
||||
await page.waitForTimeout(300);
|
||||
await audioBtn.click();
|
||||
await page.waitForTimeout(3000);
|
||||
console.log('✅ Played ayah audio');
|
||||
} else {
|
||||
console.log('⚠️ Audio button not visible, skipping');
|
||||
}
|
||||
|
||||
// Go back to surah list
|
||||
await page.click('.back-btn');
|
||||
await page.waitForTimeout(800);
|
||||
|
||||
// --- HIJRI TAB ---
|
||||
await page.hover('nav .tab:has-text("Hijri")');
|
||||
await page.waitForTimeout(300);
|
||||
await page.click('nav .tab:has-text("Hijri")');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await expect(page.locator('.card h2')).toHaveText('📅 Hijri Calendar');
|
||||
await page.waitForSelector('.hijri-day', { timeout: 10000 });
|
||||
|
||||
const hijriDay = await page.locator('.hijri-day').textContent();
|
||||
console.log(`✅ Hijri date: ${hijriDay} ${await page.locator('.hijri-month').textContent()} ${await page.locator('.hijri-year').textContent()}`);
|
||||
|
||||
// Scroll through info rows
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await page.hover(`.info-row:nth-child(${i + 1})`);
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
|
||||
// --- TASBIH TAB ---
|
||||
await page.hover('nav .tab:has-text("Tasbih")');
|
||||
await page.waitForTimeout(300);
|
||||
await page.click('nav .tab:has-text("Tasbih")');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await expect(page.locator('.card h2')).toHaveText('📿 Tasbih Counter');
|
||||
|
||||
// Count 33 (one full cycle)
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await page.click('.counter-ring');
|
||||
await page.waitForTimeout(150);
|
||||
}
|
||||
await expect(page.locator('.counter-ring text').first()).toHaveText('5');
|
||||
console.log('✅ Tasbih counter incremented');
|
||||
|
||||
// Change target to 99
|
||||
await page.selectOption('#bead-select', '99');
|
||||
await page.waitForTimeout(300);
|
||||
console.log('✅ Changed target to 99');
|
||||
|
||||
// Reset
|
||||
await page.click('button.secondary:has-text("Reset")');
|
||||
await expect(page.locator('.counter-ring text').first()).toHaveText('0');
|
||||
console.log('✅ Reset counter');
|
||||
|
||||
// --- 99 NAMES TAB ---
|
||||
await page.hover('nav .tab:has-text("99 Names")');
|
||||
await page.waitForTimeout(300);
|
||||
await page.click('nav .tab:has-text("99 Names")');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await expect(page.locator('.card h2')).toHaveText('💚 99 Names of Allah');
|
||||
await page.waitForSelector('.name-card', { timeout: 10000 });
|
||||
|
||||
const nameCount = await page.locator('.name-card').count();
|
||||
expect(nameCount).toBe(99);
|
||||
console.log(`✅ 99 Names loaded (${nameCount} names)`);
|
||||
|
||||
// Scroll through first 10
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await page.hover(`.name-card:nth-child(${i + 1})`);
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
// Expand first name
|
||||
await page.click('.name-card:first-child');
|
||||
await page.waitForTimeout(500);
|
||||
await expect(page.locator('.name-card.expanded .name-meaning')).toHaveText('The Most Merciful');
|
||||
console.log('✅ Expanded first name (Ar-Rahman)');
|
||||
|
||||
// Search
|
||||
await page.fill('.search-input', 'Rahman');
|
||||
await page.waitForTimeout(500);
|
||||
const filtered = await page.locator('.name-card').evaluateAll(cards =>
|
||||
cards.filter(c => c.offsetParent !== null).length
|
||||
);
|
||||
expect(filtered).toBeGreaterThanOrEqual(1);
|
||||
console.log(`✅ Search "Rahman" returned ${filtered} results`);
|
||||
|
||||
// Clear search
|
||||
await page.fill('.search-input', '');
|
||||
await page.waitForTimeout(300);
|
||||
await expect(page.locator('.name-card')).toHaveCount(99);
|
||||
|
||||
// --- PWA VERIFICATION ---
|
||||
await page.goto('/');
|
||||
await page.waitForSelector('.brand-name:has-text("Nur")');
|
||||
|
||||
const manifest = page.locator('link[rel="manifest"]').first();
|
||||
await expect(manifest).toHaveAttribute('href', '/manifest.webmanifest');
|
||||
|
||||
const themeColor = page.locator('meta[name="theme-color"]');
|
||||
await expect(themeColor).toHaveAttribute('content', '#070A0D');
|
||||
|
||||
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');
|
||||
console.log('✅ PWA manifest & meta tags verified');
|
||||
|
||||
console.log('\n🎉 FULL HUMAN-LIKE UAT JOURNEY COMPLETE');
|
||||
console.log('All 6 tabs tested with realistic interactions ✅');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user