Files
Native-Router-Vue/apps/demo/e2e/pwa.spec.ts
2026-07-22 02:12:05 +00:00

158 lines
5.0 KiB
TypeScript

import { expect, test } from "@playwright/test";
const iphoneUserAgent =
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 Version/18.5 Mobile/15E148 Safari/604.1";
async function dispatchTouchStart(
page: import("@playwright/test").Page,
clientX: number,
) {
return await page.locator(".nvr-navigator").evaluate((element, x) => {
const event = new Event("touchstart", { bubbles: true, cancelable: true });
Object.defineProperty(event, "touches", {
value: [{ identifier: 7, clientX: x, clientY: 240 }],
});
return {
dispatched: element.dispatchEvent(event),
prevented: event.defaultPrevented,
};
}, clientX);
}
test("ships an installable standalone manifest and iOS metadata", async ({
page,
request,
}) => {
await page.goto("/inbox");
await expect(
page.locator('meta[name="apple-mobile-web-app-capable"]'),
).toHaveAttribute("content", "yes");
await expect(
page.locator('meta[name="apple-mobile-web-app-status-bar-style"]'),
).toHaveAttribute("content", "black-translucent");
const touchIconPath = await page
.locator('link[rel="apple-touch-icon"]')
.evaluate(
(link) =>
new URL(link.getAttribute("href") ?? "", document.baseURI).pathname,
);
expect(touchIconPath).toBe("/apple-touch-icon.png");
const manifestResponse = await request.get("/manifest.webmanifest");
expect(manifestResponse.ok()).toBe(true);
const manifest = await manifestResponse.json();
expect(manifest).toMatchObject({
id: "/",
scope: "/",
start_url: "/",
display: "standalone",
});
expect(manifest.icons).toEqual(
expect.arrayContaining([
expect.objectContaining({
src: "/pwa-192.png",
sizes: "192x192",
type: "image/png",
}),
expect.objectContaining({
src: "/pwa-512.png",
sizes: "512x512",
type: "image/png",
}),
]),
);
expect(
(await request.get("/apple-touch-icon.png")).headers()["content-type"],
).toContain("image/png");
const workerResponse = await request.get("/sw.js");
expect(workerResponse.ok()).toBe(true);
expect(workerResponse.headers()["cache-control"]).toContain("no-cache");
const worker = await workerResponse.text();
expect(worker).toContain("self.skipWaiting()");
expect(worker).toContain("clientsClaim()");
});
test("does not interfere with Safari edge touches before Home Screen installation", async ({
page,
}) => {
await page.addInitScript((userAgent) => {
Object.defineProperty(navigator, "userAgent", {
configurable: true,
value: userAgent,
});
Object.defineProperty(navigator, "standalone", {
configurable: true,
value: false,
});
}, iphoneUserAgent);
await page.goto("/chat/maya");
await expect(page.locator("html")).toHaveAttribute(
"data-pwa-display-mode",
"browser",
);
await expect(page.locator("html")).toHaveAttribute(
"data-pwa-edge-guard",
"inactive",
);
expect((await dispatchTouchStart(page, 1)).prevented).toBe(false);
});
test("reserves only the leading edge in an installed iOS PWA", async ({
page,
}) => {
await page.addInitScript((userAgent) => {
Object.defineProperty(navigator, "userAgent", {
configurable: true,
value: userAgent,
});
Object.defineProperty(navigator, "standalone", {
configurable: true,
value: true,
});
}, iphoneUserAgent);
await page.goto("/chat/maya");
const root = page.locator("html");
await expect(root).toHaveAttribute("data-pwa-platform", "ios");
await expect(root).toHaveAttribute("data-pwa-display-mode", "standalone");
await expect(root).toHaveAttribute("data-pwa-edge-guard", "active");
expect((await dispatchTouchStart(page, 80)).prevented).toBe(false);
expect((await dispatchTouchStart(page, 1)).prevented).toBe(true);
await expect(root).toHaveAttribute("data-pwa-edge-claims", "1");
});
test("registers and activates the offline service worker", async ({ page }) => {
await page.goto("/inbox");
const workerUrl = await page.evaluate(async () => {
const registration = await navigator.serviceWorker.ready;
return registration.active?.scriptURL ?? "";
});
expect(workerUrl).toMatch(/\/sw\.js$/);
await expect(page.locator("html")).not.toHaveAttribute(
"data-pwa-update-checks",
"0",
);
});
test("precaches lazily split routes for offline navigation", async ({
page,
}) => {
await page.goto("/inbox");
await page.evaluate(async () => {
await navigator.serviceWorker.ready;
});
const cachedUrls = await page.evaluate(async () => {
const urls: string[] = [];
for (const name of await caches.keys()) {
const cache = await caches.open(name);
urls.push(...(await cache.keys()).map((request) => request.url));
}
return urls;
});
expect(cachedUrls.some((url) => /StoriesView-.*\.js$/.test(url))).toBe(true);
expect(cachedUrls.some((url) => /ProfileView-.*\.js$/.test(url))).toBe(true);
expect(cachedUrls.some((url) => /RuntimeLabView-.*\.js$/.test(url))).toBe(
true,
);
});