import { test, expect, Page } from "@playwright/test" import matchPath from "./urlMatcher" import { messageSW } from "@remix-pwa/sw" const { describe, beforeEach, skip, use } = test function webkitOnly() { beforeEach(async ({ browserName }) => { if (browserName !== "webkit") skip() }) } function nonWebkitOnly() { beforeEach(async ({ browserName }) => { if (browserName === "webkit") skip() }) } describe("when user is on iOS", () => { webkitOnly() describe("version 16.4 or higher", () => { use({ userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Mobile/15E148 Safari/604.1", }) describe("and the browser is not safari", () => { use({ userAgent: "Mozilla/5.0 (iPhone; U; CPU iPhone OS 16_4 like Mac OS X; en-gb) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3", }) test("the user is told they need to install through Safari, because reasons, I guess", async ({ page, }) => { await page.goto("/") const safariText = await page.getByText(/Open tackupnow.com in Safari/) await expect(safariText).toBeAttached() }) }) test("and tack up now is not running as a PWA, they are instructed to install tack up now", async ({ page, }) => { await page.goto("/") const installText = await page.getByText(/Install Tack Up Now!/) await expect(installText).toBeAttached() }) describe("and tack up now is running as a PWA", () => { beforeEach(async ({ page }) => { await page.addInitScript( () => ((window.navigator as any)["standalone"] = true) ) await stubServiceWorker(page) }) describe("and notifications aren't enabled", () => { test("they are asked to enable notifications", async ({ page }) => { await stubNotifications(page, { permission: "default" }) await page.goto("/") const enableButton = await page.getByText(/Enable Notifications/) await expect(enableButton).toBeAttached() }) describe("and then the user enables notifications", () => { beforeEach(async ({ page }) => { await stubNotifications(page, { permission: "default", requestPermissionResult: "granted", }) }) test("users see tack up now after enabling notifications", async ({ page, }) => { await page.goto("/") await page.getByText(/Enable Notifications/).click() const yourNotificationsHeading = page.getByText(/Your Notifications/) await expect(yourNotificationsHeading).toBeVisible() }) test("the subscription is submitted", async ({ page }) => { await page.goto("/") await page.getByText(/Enable Notifications/).click() const postedMessages = await page.evaluate(() => { return (window as any).postedMessages }) expect(postedMessages).toContainEqual({ type: "subscribed", subscription: { subscription: "HI", }, }) }) }) }) test("and notifications have been denied", async ({ page }) => { await stubNotifications(page, { permission: "denied" }) await page.goto("/") const deniedText = page.getByText(/requires notifications permissions/) await expect(deniedText).toBeAttached() }) describe("and notifications are enabled", () => { beforeEach(async ({ page }) => { await stubNotifications(page, { permission: "granted" }) }) test("they aren't asked to enable notifications", async ({ page }) => { await page.goto("/") await page.evaluate(async () => await navigator.serviceWorker.ready) const notificationText = page.getByText(/Enable Notifications/) await expect(notificationText).not.toBeAttached() }) test("users see tack up now", async ({ page }) => { await page.goto("/") const yourNotificationsHeading = page.getByText(/Your Notifications/) await expect(yourNotificationsHeading).toBeVisible() }) }) }) }) describe("version 16.3 and under", () => { use({ userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Mobile/15E148 Safari/604.1", }) test("version 16.3 and under they are informed that their iOS version isn't supported", async ({ page, }) => { await page.goto("/") const sorryText = page.getByText(/Sorry/) await expect(sorryText).toBeVisible() }) }) }) describe("other browsers", () => { nonWebkitOnly() beforeEach(async ({ page }) => { await page.route(matchPath(page, "/api/subscription"), async (route) => { await route.fulfill() }) await stubServiceWorker(page) }) test("prompt the user to allow notifications", async ({ page }) => { await stubNotifications(page, { permission: "default" }) await page.goto("/") const notificationText = page.getByText(/Enable Notifications/) await expect(notificationText).toBeAttached() }) test("show tack up now when permission is granted", async ({ page }) => { await stubNotifications(page, { permission: "granted" }) await page.goto("/") const yourNotificationsHeading = page.getByText(/Your Notifications/) await expect(yourNotificationsHeading).toBeVisible() }) test("prompt to allow notifications if permission was denied", async ({ page, }) => { await stubNotifications(page, { permission: "denied" }) await page.goto("/") const deniedText = page.getByText(/requires notifications permissions/) await expect(deniedText).toBeAttached() }) }) function stubNotifications( page: Page, args: { permission: NotificationPermission requestPermissionResult?: NotificationPermission } ) { return page.addInitScript( (args: { permission: NotificationPermission requestPermissionResult?: NotificationPermission }) => { window.Notification = window.Notification ?? {} Object.defineProperty(window.Notification, "permission", { value: args.permission, writable: true, }) Object.defineProperty(window.Notification, "requestPermission", { value: () => Promise.resolve(args.requestPermissionResult ?? args.permission), }) }, args ) } function stubServiceWorker(page: Page) { return page.addInitScript(() => { function createSubscription(testToken = "HI") { return { toJSON: () => ({ subscription: testToken, }), } } let postedMessages: any[] = [] const serviceWorker = { postMessage: (message: any) => postedMessages.push(message), } const registration = { pushManager: { getSubscription() { return Promise.resolve(createSubscription()) }, subscribe(args: Parameters[0]) { return Promise.resolve(createSubscription()) }, }, active: serviceWorker, } Object.defineProperty(window, "postedMessages", { value: postedMessages, writable: true, }) Object.defineProperty(navigator, "serviceWorker", { value: { ready: Promise.resolve(registration), register() { return Promise.resolve(registration) }, getRegistration() { return Promise.resolve(registration) }, addEventListener() {}, removeEventListener() {}, }, writable: false, }) }) }