Files
TrueNews/tests/browser/notifications.spec.mjs

62 lines
4.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {test,expect} from '@playwright/test';
// Mock permission/subscription APIs, not the page UI or backend subscription API.
// Browser vendor push enrollment requires real user/device consent and credentials.
async function mockPush(page,{denied=false,failSave=false}={}){
await page.addInitScript(({denied})=>{
let current=null;
let permission=denied?'denied':'default';
const encode=bytes=>btoa(String.fromCharCode(...bytes)).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'');
const key=new Uint8Array(65);key[0]=4;key.fill(1,1);
const subscription={endpoint:'https://fcm.googleapis.com/fcm/send/browser-test',keys:{p256dh:encode(key),auth:encode(new Uint8Array(16).fill(2))}};
const manager={getSubscription:async()=>current,subscribe:async()=>{
current={toJSON:()=>subscription,unsubscribe:async()=>{current=null;return true;}};return current;
}};
const registration={pushManager:manager};
Object.defineProperty(Notification,'permission',{get:()=>permission});
Notification.requestPermission=async()=>{window.permissionRequests=(window.permissionRequests||0)+1;permission=denied?'denied':'granted';return permission;};
Object.defineProperty(navigator.serviceWorker,'register',{value:async()=>registration});
Object.defineProperty(navigator.serviceWorker,'ready',{value:Promise.resolve(registration)});
window.PushManager ||= function(){};
},{denied});
if(failSave)await page.route('**/api/push/subscribe',route=>route.fulfill({status:500,body:'{}',contentType:'application/json'}));
}
test('invitation is dismissible, fits mobile, and never asks permission on page load',async({page})=>{
await page.setViewportSize({width:390,height:844});await mockPush(page);await page.goto('/');
await expect(page.locator('#push-prompt')).toBeVisible();
expect(await page.evaluate(()=>window.permissionRequests||0)).toBe(0);
const box=await page.locator('#push-prompt').boundingBox();expect(box.x).toBeGreaterThanOrEqual(0);expect(box.x+box.width).toBeLessThanOrEqual(390);
await page.getByRole('button',{name:'Not now'}).click();await expect(page.locator('#push-prompt')).toBeHidden();
await page.reload();await expect(page.locator('#push-toggle')).toBeVisible();await expect(page.locator('#push-prompt')).toBeHidden();
await page.locator('#push-toggle').click();await expect(page.locator('#push-prompt')).toBeVisible();
});
test('enable and disable save preferences through the real API',async({page})=>{
await mockPush(page);await page.goto('/');await page.getByRole('button',{name:'Enable notifications',exact:true}).click();
await expect(page.locator('#push-status')).toContainText('You’re subscribed');expect(await page.evaluate(()=>window.permissionRequests)).toBe(1);
await expect(page.locator('#push-toggle')).toHaveText('Notifications on');
await page.getByRole('button',{name:'Turn off notifications'}).click();await expect(page.locator('#push-status')).toContainText('turned off');
});
test('denied permission is not prompted again on load',async({page})=>{
await mockPush(page,{denied:true});await page.goto('/');await expect(page.locator('#push-toggle')).toBeVisible();
await expect(page.locator('#push-prompt')).toBeHidden();expect(await page.evaluate(()=>window.permissionRequests||0)).toBe(0);
await page.locator('#push-toggle').click();await page.getByRole('button',{name:'Enable notifications',exact:true}).click();
await expect(page.locator('#push-status')).toContainText('browser’s site settings');
});
test('failed subscription persistence never claims success',async({page})=>{
await mockPush(page,{failSave:true});await page.goto('/');await page.getByRole('button',{name:'Enable notifications',exact:true}).click();
await expect(page.locator('#push-status')).toContainText('Could not save');await expect(page.locator('#push-toggle')).toHaveText('Get new stories');
});
test('service worker and app manifest are served from the root',async({request})=>{
const worker=await request.get('/sw.js');expect(worker.status()).toBe(200);expect(worker.headers()['content-type']).toContain('javascript');expect(worker.headers()['cache-control']).toBe('no-cache');
const manifest=await request.get('/manifest.webmanifest');expect(manifest.status()).toBe(200);expect((await manifest.json()).display).toBe('standalone');
const privateData=await request.get('/.data/push/vapid.json');expect(privateData.status()).toBe(404);
});
test('a real service worker registers with site-wide scope',async({page})=>{
await page.goto('/');
const registration=await page.evaluate(async()=>{
const worker=await navigator.serviceWorker.ready;
return {scope:worker.scope,script:worker.active.scriptURL};
});
expect(new URL(registration.scope).pathname).toBe('/');
expect(new URL(registration.script).pathname).toBe('/sw.js');
});