Newer
Older
bugtrail / packages / web / verify-lightbox.mjs
/** Verify the report-page lightbox + attachments tab with a LARGE screenshot. */
import { chromium } from "playwright-core";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { homedir } from "node:os";

const root = dirname(fileURLToPath(import.meta.url));
const SERVER = "http://localhost:8001";
const WEB = "http://localhost:5173";
const stamp = Date.now();
const EMAIL = `lb-verify-${stamp}@example.com`;
const PASSWORD = "lb-verify-pass";

const executablePath = join(homedir(), ".cache/ms-playwright/chromium-1234/chrome-linux64/chrome");
const browser = await chromium.launch({ executablePath, headless: true });

async function api(path, options = {}) {
  const r = await fetch(SERVER + path, options);
  if (!r.ok) throw new Error(`API ${path} -> ${r.status}`);
  return r.json();
}

const user = await api("/api/auth/register", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ nickname: `lb-verify-${stamp}`, email: EMAIL, password: PASSWORD }),
});
const login = await api("/api/auth/login", {
  method: "POST",
  headers: { "Content-Type": "application/json", "X-Client": "extension" },
  body: JSON.stringify({ email: EMAIL, password: PASSWORD }),
});
const auth = { Authorization: `Bearer ${login.token}` };
const project = await api("/api/projects", {
  method: "POST",
  headers: { "Content-Type": "application/json", ...auth },
  body: JSON.stringify({ name: "Lightbox Verify" }),
});

// big screenshot: 1400x900 viewport @2x DPR → 2800x1800 natural
const maker = await browser.newPage({ viewport: { width: 1400, height: 900 }, deviceScaleFactor: 2 });
await maker.setContent(`<body style="margin:0;background:linear-gradient(135deg,#1f2335,#7aa2f7)">
  <h1 style="color:#c0caf5;font:700 60px monospace;padding:80px">Big screenshot 2800×1800</h1></body>`);
const png = await maker.screenshot({ path: "/tmp/ltt-shots/big.png" });
await maker.close();

const form = new FormData();
form.append("file", new Blob([png], { type: "image/png" }), "big.png");
const up = await api("/api/uploads", { method: "POST", headers: auth, body: form });
const note = await api("/api/reports", {
  method: "POST",
  headers: { "Content-Type": "application/json", ...auth },
  body: JSON.stringify({
    project_id: project.id,
    type: "element_note",
    title: "Lightbox verify",
    environment: { user_agent: "verify", browser: "Chromium", os: "Linux", viewport: { w: 1400, h: 900 }, dpr: 2, language: "en" },
    attachment_ids: [up.file_id],
  }),
});

const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto(`${WEB}/r/${note.share_token}`);
await page.waitForSelector(".screenshot-expand", { timeout: 15000 });
await page.waitForTimeout(800);
await page.screenshot({ path: "/tmp/ltt-shots/08-report.png" });

// attachments tab preview (Правка 2)
await page.click("text=Вложения");
await page.waitForTimeout(800);
console.log("widths:", await page.evaluate(() => {
  const r = (el) => (el ? (({ x, width }) => `${Math.round(x)}+${Math.round(width)}`)(el.getBoundingClientRect()) : "none");
  const tabs = document.querySelector(".tabs");
  const panel = document.querySelector(".tab-panel-active");
  const cs = panel ? getComputedStyle(panel) : null;
  return {
    tabs: r(tabs),
    tabsMaxWidth: tabs ? getComputedStyle(tabs).maxWidth : null,
    panel: r(panel),
    panelWidth: cs?.width,
    panelParent: r(panel?.parentElement),
    panelParentTag: panel?.parentElement?.className,
    card: r(document.querySelector(".tab-panel-active .card")),
  };
}));
await page.screenshot({ path: "/tmp/ltt-shots/09-attachments.png" });
await page.click("text=Детали");

// open the lightbox (Правка 3)
await page.click(".screenshot-expand");
await page.waitForSelector(".lightbox", { timeout: 5000 });
await page.waitForTimeout(1000);
const zoom = page.locator(".lightbox-zoom");
console.log("fit zoom (expect <100% for 2800px image):", (await zoom.textContent()).trim());
await page.screenshot({ path: "/tmp/ltt-shots/10-lightbox-fit.png" });

// wheel zoom toward the cursor
await page.mouse.move(700, 400);
for (let i = 0; i < 3; i++) await page.mouse.wheel(0, -120);
await page.waitForTimeout(300);
console.log("after wheel zoom-in:", (await zoom.textContent()).trim());

// pan by dragging (drag ends over the backdrop — must NOT close)
const before = await page.locator(".lightbox-img").getAttribute("style");
await page.mouse.move(700, 450);
await page.mouse.down();
await page.mouse.move(400, 250, { steps: 8 });
await page.mouse.up();
const after = await page.locator(".lightbox-img").getAttribute("style");
console.log("pan moved transform:", before !== after ? "YES" : "NO");
console.log("lightbox still open after drag:", (await page.locator(".lightbox").count()) === 1);
await page.screenshot({ path: "/tmp/ltt-shots/11-lightbox-panned.png" });

// zoom cap: hammer zoom-in, must never exceed 100% (natural resolution)
for (let i = 0; i < 12; i++) await page.click('.lightbox-btn[title="Zoom in"]');
console.log("after 12 more zoom-ins (cap):", (await zoom.textContent()).trim());

// toolbar zoom-out works
await page.click('.lightbox-btn[title="Zoom out"]');
await page.click('.lightbox-btn[title="Zoom out"]');
console.log("after 2x zoom-out:", (await zoom.textContent()).trim());

// Esc closes
await page.keyboard.press("Escape");
await page.waitForTimeout(300);
console.log("lightbox closed on Esc:", (await page.locator(".lightbox").count()) === 0);

await browser.close();
console.log("VERIFY OK");