import { createHash } from 'node:crypto'; import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'; import { resolve } from 'node:path'; import { chromium } from '@playwright/test'; const baseUrl = process.env.REF_MAIN_URL ?? 'http://127.0.0.1:3400/sam/'; const username = process.env.REF_USER_ID ?? 'refuser1'; const passwordFile = process.env.REF_USER_PASSWORD_FILE ?? '/home/letrhee/sam_rebuild/docker_compose_files/reference/secrets/user1_password'; const artifactRoot = resolve(process.env.REF_PROGRESS_ARTIFACT_DIR ?? 'artifacts/ref-progress-bars'); const password = (await readFile(passwordFile, 'utf8')).trim(); await mkdir(artifactRoot, { recursive: true, mode: 0o700 }); const browser = await chromium.launch({ headless: true }); try { const context = await browser.newContext({ colorScheme: 'dark', deviceScaleFactor: 1, locale: 'ko-KR', timezoneId: 'UTC', }); const page = await context.newPage(); await page.goto(baseUrl, { waitUntil: 'networkidle' }); const globalSalt = await page.locator('#global_salt').inputValue(); const passwordHash = createHash('sha512') .update(globalSalt + password + globalSalt) .digest('hex'); const response = await context.request.post(new URL('api.php?path=Login/LoginByID', baseUrl).toString(), { data: { username, password: passwordHash }, }); if (!response.ok()) throw new Error(`reference login failed: HTTP ${response.status()}`); const loginPayload = await response.json(); if (loginPayload?.result !== true) { throw new Error(`reference login rejected: ${String(loginPayload?.reason ?? loginPayload)}`); } if (process.env.REF_CREATE_GENERAL === '1') { await page.goto(new URL('hwe/v_join.php', baseUrl).toString(), { waitUntil: 'networkidle' }); const createButton = page.getByRole('button', { name: '장수 생성', exact: true }); if (await createButton.isVisible()) { page.on('dialog', (dialog) => dialog.accept()); await createButton.click(); await page.waitForURL(/\/hwe\/?$/u); } } for (const viewport of [ { name: 'desktop-1000', width: 1000, height: 900 }, { name: 'mobile-500', width: 500, height: 900 }, ]) { await page.setViewportSize(viewport); await page.goto(new URL('hwe/', baseUrl).toString(), { waitUntil: 'networkidle' }); try { await page .locator('.city-card-basic .sammo-bar, .bar_out') .first() .waitFor({ state: 'visible', timeout: 8_000 }); } catch { throw new Error( `reference main progress bars missing: ${JSON.stringify({ url: page.url(), title: await page.title(), cityCards: await page.locator('.city-card-basic').count(), generalCards: await page.locator('.general-card-basic').count(), legacyBars: await page.locator('.bar_out').count(), })}` ); } const measurement = await page.evaluate(() => { const inspect = (element) => { const rect = element.getBoundingClientRect(); const style = getComputedStyle(element); const fill = element.querySelector('.sammo-bar-in'); const fillStyle = fill ? getComputedStyle(fill) : null; return { rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }, borderTop: style.borderTop, borderBottom: style.borderBottom, backgroundImage: getComputedStyle(element.querySelector('.sammo-bar-base')).backgroundImage, fillWidth: fill?.getBoundingClientRect().width ?? 0, fillBackgroundImage: fillStyle?.backgroundImage ?? '', }; }; return { viewport: { width: innerWidth, height: innerHeight }, city: [...document.querySelectorAll('.city-card-basic .sammo-bar')].map(inspect), general: [...document.querySelectorAll('.general-card-basic .sammo-bar')].map(inspect), cityCard: document.querySelector('.city-card-basic').getBoundingClientRect().toJSON(), generalCard: document.querySelector('.general-card-basic').getBoundingClientRect().toJSON(), }; }); await Promise.all([ page.screenshot({ path: resolve(artifactRoot, `ref-${viewport.name}.png`), fullPage: true }), writeFile(resolve(artifactRoot, `ref-${viewport.name}.json`), `${JSON.stringify(measurement, null, 2)}\n`, { mode: 0o600, }), ]); await chmod(resolve(artifactRoot, `ref-${viewport.name}.png`), 0o600); console.log( JSON.stringify({ viewport: viewport.name, cityBars: measurement.city.length, generalBars: measurement.general.length, cityHeight: measurement.city[0]?.rect.height, generalHeight: measurement.general[0]?.rect.height, }) ); } } finally { await browser.close(); }