87 lines
3.8 KiB
JavaScript
87 lines
3.8 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { chromium } from '@playwright/test';
|
|
import { mkdtemp, rm } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { resolve } from 'node:path';
|
|
import { createApp } from '../../apps/server/src/app.ts';
|
|
import { loadConfig } from '../../apps/server/src/config.ts';
|
|
const folder = await mkdtemp(resolve(tmpdir(), 'hop-pwa-'));
|
|
const runtime = await createApp({
|
|
...loadConfig({
|
|
APP_URL: 'http://127.0.0.1',
|
|
DATABASE_PATH: resolve(folder, 'db.sqlite'),
|
|
UPLOAD_DIR: resolve(folder, 'uploads'),
|
|
MIN_FREE_DISK_MB: '0',
|
|
SESSION_SECRET: 's'.repeat(40),
|
|
DISCOVERY_SECRET: 'd'.repeat(40),
|
|
}),
|
|
webRoot: resolve('apps/web/dist'),
|
|
});
|
|
let browser;
|
|
try {
|
|
await runtime.app.listen({ host: '127.0.0.1', port: 0 });
|
|
const url = `http://127.0.0.1:${runtime.app.server.address().port}`;
|
|
runtime.setDevOrigin(url);
|
|
browser = await chromium.launch({
|
|
headless: true,
|
|
...(process.env.HOP_BROWSER_EXECUTABLE
|
|
? { executablePath: process.env.HOP_BROWSER_EXECUTABLE }
|
|
: {}),
|
|
});
|
|
const context = await browser.newContext({
|
|
viewport: { width: 390, height: 844 },
|
|
userAgent:
|
|
'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 Version/18.0 Mobile/15E148 Safari/604.1',
|
|
});
|
|
const page = await context.newPage();
|
|
await page.addInitScript(() => {
|
|
window.addEventListener('beforeinstallprompt', (event) => {
|
|
if (!event.__hopTest) { event.preventDefault(); event.stopImmediatePropagation(); }
|
|
}, { capture: true });
|
|
});
|
|
await page.goto(url);
|
|
await page.locator('.app-shell[data-online="true"]').waitFor();
|
|
const manifest = await (await context.request.get(`${url}/manifest.webmanifest`)).json();
|
|
assert.equal(manifest.display, 'standalone');
|
|
assert.equal(manifest.scope, '/');
|
|
for (const icon of manifest.icons)
|
|
assert.equal((await context.request.get(url + icon.src)).status(), 200);
|
|
await page.waitForFunction(() => navigator.serviceWorker.controller !== null);
|
|
assert.deepEqual(await page.evaluate(() => caches.keys()), []);
|
|
const install = page.getByRole('button', { name: 'hop. 앱 설치', exact: true });
|
|
await install.click();
|
|
await page.getByRole('dialog', { name: '앱 설치 안내' }).waitFor();
|
|
assert.match(await page.locator('.install-help').innerText(), /Safari.*공유/s);
|
|
await page.screenshot({ path: 'tests/e2e/pwa-ios.png' });
|
|
await page.keyboard.press('Escape');
|
|
await page.getByRole('dialog', { name: '앱 설치 안내' }).waitFor({ state: 'hidden' });
|
|
await page.setViewportSize({ width: 320, height: 700 });
|
|
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true);
|
|
await page.evaluate(() => {
|
|
window.__installCalls = 0;
|
|
const event = new Event('beforeinstallprompt', { cancelable: true });
|
|
event.__hopTest = true;
|
|
event.prompt = async () => {
|
|
window.__installCalls++;
|
|
};
|
|
event.userChoice = Promise.resolve({ outcome: 'accepted' });
|
|
window.dispatchEvent(event);
|
|
});
|
|
await install.click();
|
|
assert.equal(await page.evaluate(() => window.__installCalls), 1);
|
|
await install.waitFor({ state: 'hidden' });
|
|
await page.reload();
|
|
assert.equal(await page.evaluate(async () => (await fetch('/api/device/me')).status), 200);
|
|
await context.setOffline(true);
|
|
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
assert.match(await page.locator('body').innerText(), /인터넷 연결이 필요/);
|
|
assert.deepEqual(await page.evaluate(() => caches.keys()), []);
|
|
console.log(
|
|
'PASS: 설치 manifest·아이콘, 실제 Service Worker, iPhone 홈 화면 안내, 설치 이벤트와 설치 후 숨김, 320px 헤더, 온라인 API와 개인정보 캐시 없음, 오프라인 안내',
|
|
);
|
|
} finally {
|
|
await browser?.close();
|
|
await runtime.app.close();
|
|
await rm(folder, { recursive: true, force: true });
|
|
}
|