/* One account, two devices. * * Everything here is written on one machine and read on another, and the parts * travel differently: a note's text is an encrypted column, a drawing is a * second one, and a PDF is a file the note only points at. That last is the one * worth proving — the bytes are uploaded once and fetched by id, so a device * that has never seen the document has to be able to ask the server for it. * * A second browser context is a second device: its own storage, its own cache, * its own session. */ import { newPage, signIn, check, createNotebook, launch, report, withFreshUser } from "./lib.mjs"; import { writeFileSync } from "node:fs"; const PDF = "/tmp/sync-test.pdf"; const s1 = "BT /F1 22 Tf 60 700 Td (Handbuch Seite eins) Tj ET"; const objs = [ "<< /Type /Catalog /Pages 2 0 R >>", "<< /Type /Pages /Kids [3 0 R] /Count 1 >>", "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>", `<< /Length ${s1.length} >>\nstream\n${s1}\nendstream`, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>", ]; let out = "%PDF-1.4\n"; const offs = []; objs.forEach((o, i) => { offs.push(out.length); out += `${i + 1} 0 obj\n${o}\nendobj\n`; }); const xref = out.length; out += `xref\n0 ${objs.length + 1}\n0000000000 65535 f \n`; for (const o of offs) out += `${String(o).padStart(10, "0")} 00000 n \n`; out += `trailer\n<< /Size ${objs.length + 1} /Root 1 0 R >>\nstartxref\n${xref}\n%%EOF\n`; writeFileSync(PDF, out, "latin1"); const browser = await launch(); let s; try { s = await withFreshUser(browser, "sync"); const pc = s.page; await createNotebook(pc, "Sync"); await pc.locator("button").filter({ hasText: "Sync" }).first().click(); await pc.waitForTimeout(700); const chooser = pc.waitForEvent("filechooser"); await pc.locator("[data-add-menu]").click(); await pc.waitForTimeout(400); await pc.locator('[data-add-option="Upload PDF"]').click(); await (await chooser).setFiles(PDF); await pc.waitForTimeout(5000); check((await pc.locator("[data-pdf-page]").count()) > 0, "device A imported the PDF"); // draw a stroke and pin a note await pc.locator("[data-pdf-mode]").click(); await pc.waitForTimeout(500); const box = await pc.locator('[data-pdf-page="1"]').boundingBox(); await pc.mouse.move(box.x + 80, box.y + 200); await pc.mouse.down(); await pc.mouse.move(box.x + 260, box.y + 240, { steps: 10 }); await pc.mouse.up(); await pc.waitForTimeout(2000); await pc.locator("[data-pdf-mode]").click(); await pc.waitForTimeout(500); await pc.locator("[data-pdf-pin-mode]").click(); await pc.waitForTimeout(400); await pc.mouse.click(box.x + box.width * 0.5, box.y + box.height * 0.3); await pc.waitForTimeout(800); await pc.getByLabel("Pinned note").fill("Auf dem PC geschrieben"); await pc.getByRole("button", { name: "Done" }).click(); await pc.waitForTimeout(3000); /* ── a second device: its own browser context, its own storage ── */ const phone = await newPage(browser, 1400, 950); await signIn(phone, s.user, s.pass); await phone.locator("button").filter({ hasText: "Sync" }).first().click(); await phone.waitForTimeout(1500); await phone.locator("[data-note-row]").first().click(); await phone.waitForTimeout(6000); check((await phone.locator("[data-pdf-page]").count()) > 0, "and the second device gets the document itself"); const inked = await phone.locator('[data-pdf-ink="1"]').evaluate((c) => { const d = c.getContext("2d").getImageData(0, 0, c.width, c.height).data; let n = 0; for (let i = 3; i < d.length; i += 4) if (d[i] > 40) n++; return n; }); check(inked > 100, "the drawing came with it", `${inked} inked pixels`); check((await phone.locator("[data-pin]").count()) === 1, "and the pinned note"); check( (await phone.locator("[data-pin-card]").innerText()).includes("Auf dem PC geschrieben"), "with its text", await phone.locator("[data-pin-card]").innerText() ); await phone.context().close(); } catch (e) { check(false, "suite ran to the end", e.message); } finally { await s?.cleanup(); const code = report(); await browser.close(); process.exit(code); }