1import { statSync } from "node:fs"
2import path from "node:path"
3import { expect, test } from "@playwright/test"
4
5import {
6 canvasChecksum,
7 masukHias,
8 masukHasil,
9 matikanGesture,
10} from "./helpers"
11
12/* M7 (SH-01 sampai SH-03): Strip Hidup. Uji ini memakai kamera palsu Chromium,
13 jadi klip benar-benar terekam selama hitung mundur. Semuanya berjalan lokal
14 di peramban: tidak ada lagi klip yang diunggah ke server. */
15const FIXTURE_DIR = path.join(process.cwd(), "tests", "e2e", "fixtures")
16
17test.beforeEach(async ({ page }) => {
18 await matikanGesture(page)
19})
20
21test("sakelar Strip Hidup memutar klip di kanvas", async ({ page }) => {
22 await masukHias(page)
23 const sakelar = page.getByRole("switch", { name: "Strip Hidup" })
24 await expect(sakelar).toBeEnabled()
25 await expect(sakelar).not.toBeChecked()
26
27 /* Sebelum dinyalakan kanvas diam. */
28 const diamSatu = await canvasChecksum(page)
29 await page.waitForTimeout(500)
30 expect(await canvasChecksum(page)).toBe(diamSatu)
31
32 await sakelar.click()
33 await expect(sakelar).toBeChecked()
34 await expect(page.getByText("Nyala", { exact: true })).toBeVisible()
35 await expect(
36 page.getByText("Versi hidup ikut diunggah saat kamu membuat link.")
37 ).toBeVisible()
38
39 /* SH-02: pratinjau benar-benar berputar, bukan hanya lencananya muncul.
40 Penggambaran ulang kanvas tidak sinkron dengan klik, jadi hasilnya
41 ditunggu, bukan dibandingkan tepat setelah sakelar dinyalakan (M9). */
42 await expect.poll(() => canvasChecksum(page)).not.toBe(diamSatu)
43
44 await sakelar.click()
45 await expect(sakelar).not.toBeChecked()
46 /* Kembali ke foto tetap: nilainya sama dengan sebelum dinyalakan. */
47 await expect.poll(() => canvasChecksum(page)).toBe(diamSatu)
48 const matiSatu = await canvasChecksum(page)
49 await page.waitForTimeout(500)
50 expect(await canvasChecksum(page)).toBe(matiSatu)
51})
52
53test("prefers-reduced-motion: pratinjau berhenti dengan tombol Putar", async ({
54 page,
55}) => {
56 await page.emulateMedia({ reducedMotion: "reduce" })
57 await masukHias(page)
58
59 const tanpaHidup = await canvasChecksum(page)
60 await page.getByRole("switch", { name: "Strip Hidup" }).click()
61
62 /* Berhenti: bingkai klip tidak berjalan, tetapi lencana "Klip" membuktikan
63 bingkainya memang terekam (tampilannya berbeda dari saat sakelar mati).
64 Penggambaran ulangnya ditunggu karena tidak sinkron dengan klik (M9). */
65 await expect.poll(() => canvasChecksum(page)).not.toBe(tanpaHidup)
66 const berhentiSatu = await canvasChecksum(page)
67 await page.waitForTimeout(500)
68 expect(await canvasChecksum(page)).toBe(berhentiSatu)
69
70 const putar = page.getByRole("button", { name: "Putar" })
71 await expect(putar).toBeVisible()
72 await putar.click()
73 await expect(page.getByRole("button", { name: "Jeda" })).toBeVisible()
74
75 const jalanSatu = await canvasChecksum(page)
76 await expect.poll(() => canvasChecksum(page)).not.toBe(jalanSatu)
77 await page.waitForTimeout(500)
78 expect(await canvasChecksum(page)).not.toBe(jalanSatu)
79
80 await page.getByRole("button", { name: "Jeda" }).click()
81 /* Berhenti lagi pada bingkai pertama, jadi tampilannya kembali ke keadaan
82 sebelum diputar. */
83 await expect.poll(() => canvasChecksum(page)).toBe(berhentiSatu)
84 const jedaSatu = await canvasChecksum(page)
85 await page.waitForTimeout(500)
86 expect(await canvasChecksum(page)).toBe(jedaSatu)
87})
88
89test("tanpa klip: sakelar nonaktif dengan penjelasannya", async ({ page }) => {
90 /* Foto dari galeri tidak punya klip, jadi Strip Hidup tidak punya bahan. */
91 await page.goto("/")
92 const chooser = page.waitForEvent("filechooser")
93 await page.getByRole("button", { name: "Pakai Foto Galeri" }).click()
94 await (await chooser).setFiles([
95 path.join(FIXTURE_DIR, "foto-1.png"),
96 path.join(FIXTURE_DIR, "foto-2.png"),
97 path.join(FIXTURE_DIR, "foto-3.png"),
98 path.join(FIXTURE_DIR, "foto-4.png"),
99 ])
100 await page.waitForURL("**/booth/customize")
101 await expect(page.locator("canvas")).toBeVisible()
102
103 const sakelar = page.getByRole("switch", { name: "Strip Hidup" })
104 await expect(sakelar).toBeDisabled()
105 await expect(sakelar).not.toBeChecked()
106 await expect(
107 page.getByText("Klip hanya terekam saat memotret lewat kamera.")
108 ).toBeVisible()
109})
110
111test("unduh video versi hidup di langkah Hasil", async ({ page }) => {
112 /* Rekaman dua putaran butuh sekitar 3 detik; batas bawaannya cukup, tetapi
113 headless menambah waktu penyandian, jadi diberi ruang. */
114 test.setTimeout(120_000)
115 await masukHasil(page)
116
117 const unduh = page.getByRole("button", { name: "Unduh Video" })
118 await expect(unduh).toBeVisible()
119 const [download] = await Promise.all([
120 page.waitForEvent("download"),
121 unduh.click(),
122 ])
123
124 await expect(
125 page.getByText("Video strip tersimpan di perangkatmu.")
126 ).toBeVisible()
127 expect(download.suggestedFilename()).toMatch(/^snapme-\d{8}-\d{4}\.(webm|mp4)$/)
128
129 /* SH-03: durasi sekitar 3 detik, berkasnya di bawah 5 MB. */
130 const berkas = await download.path()
131 if (berkas === null) {
132 throw new Error("Berkas unduhan tidak tersedia di disk")
133 }
134 const ukuran = statSync(berkas).size
135 expect(ukuran).toBeGreaterThan(0)
136 expect(ukuran).toBeLessThan(5_242_880)
137})
138