1import { expect, test } from "@playwright/test"
2
3import { masukHiasDariGaleri } from "./helpers"
4
5/* M9: uji lintas peramban (PRD §8: dua versi mayor terakhir Chrome, Edge,
6 Firefox, dan Safari).
7
8 Berkas ini sengaja tidak menyentuh kamera: perangkat media palsu adalah
9 bendera Chromium, sedangkan WebKit dan Firefox tidak punya padanannya.
10 Karena itu jalur foto dari galeri yang dipakai (FR-13), dan asersi pratinjau
11 cetak tetap di chromium-only result.spec.ts. Berkas ini dijalankan proyek
12 chromium, firefox, webkit, dan msedge (playwright.config.ts). */
13
14test("welcome memuat aksi utama dan halaman statis menjawab judulnya", async ({
15 page,
16}) => {
17 await page.goto("/")
18 await expect(
19 page.getByRole("heading", { name: "Foto strip tanpa antre." })
20 ).toBeVisible()
21 await expect(page.getByRole("link", { name: "Mulai Foto" }).first()).toBeVisible()
22
23 await page.goto("/privasi")
24 await expect(page.getByRole("heading", { level: 1 })).toContainText("Privasi")
25
26 await page.goto("/s/tidakada1234")
27 await expect(page.getByText("Halaman tidak ditemukan.")).toBeVisible()
28
29 await page.goto("/r/tidakada1234")
30 await expect(page.getByText("Halaman tidak ditemukan.")).toBeVisible()
31})
32
33test("header keamanan terpasang di rute publik dan halaman pribadi", async ({
34 page,
35}) => {
36 /* M9: CSP penuh berlaku untuk semua rute. Tanpa asersi ini, satu suntingan
37 di `next.config.ts` bisa menghapusnya tanpa ada uji yang gagal (halaman
38 tanpa CSP tetap "jalan"). */
39 const beranda = await page.goto("/")
40 const csp = beranda?.headers()["content-security-policy"] ?? ""
41 expect(csp).toContain("default-src 'self'")
42 expect(csp).toContain("frame-ancestors 'none'")
43 expect(csp).toContain("object-src 'none'")
44 expect(csp).toContain("base-uri 'self'")
45 expect(csp).toContain("wasm-unsafe-eval")
46
47 /* Produksi tidak boleh memakai 'unsafe-eval'; server pengembangan memakainya
48 untuk React, dan penandanya halaman /dev/ui (ARCHITECTURE §4). */
49 await page.goto("/dev/ui")
50 const produksi = await page.getByText("Hanya saat pengembangan.").isVisible()
51 if (produksi) {
52 expect(csp).not.toContain("'unsafe-eval'")
53 }
54
55 const privat = await page.goto("/s/tidakada1234")
56 expect(privat?.headers()["x-robots-tag"]).toBe("noindex")
57 expect(privat?.headers()["referrer-policy"]).toBe("no-referrer")
58 expect(privat?.headers()["content-security-policy"]).toBe(csp)
59})
60
61test("alur galeri sampai unduh PNG berjalan di peramban ini", async ({ page }) => {
62 test.setTimeout(120_000)
63 await masukHiasDariGaleri(page)
64
65 /* Tiga jenis hias yang mesinnya berbeda: swatch frame (kanvas), daftar
66 stiker (dek), dan input teks (formulir). */
67 await page.getByRole("button", { name: "Malam", exact: true }).click()
68 await page.getByRole("radio", { name: "Stiker" }).click()
69 await page.getByRole("button", { name: "Tempel Bintang" }).click()
70 await expect(page.getByText("Stiker Bintang")).toBeVisible()
71
72 await page.getByRole("radio", { name: "Teks" }).click()
73 await page.locator("#strip-text").fill("halo dari peramban lain")
74 await expect(page.locator("#strip-text")).toHaveValue("halo dari peramban lain")
75
76 await page.getByRole("link", { name: "Selesai & Unduh" }).click()
77 await page.waitForURL("**/booth/result")
78 await expect(page.getByAltText("Pratinjau strip")).toBeVisible({
79 timeout: 30_000,
80 })
81
82 /* Galeri tidak punya klip, jadi tombol unduh versi hidup (SH-03) memang
83 tidak ada; yang diperiksa di sini tombol cetak tetap tersedia. */
84 await expect(page.getByRole("button", { name: "Cetak 4R" })).toBeEnabled()
85
86 const [download] = await Promise.all([
87 page.waitForEvent("download", { timeout: 60_000 }),
88 page.getByRole("button", { name: "Selesai & Unduh" }).click(),
89 ])
90 expect(download.suggestedFilename()).toMatch(/^snapme-\d{8}-\d{4}\.png$/)
91})
92