docs/site-fixes-2026-10-10/{index.html,REPORT.md,screenshots}; tools/site-fixes-verify.mjs
(Playwright-проверка: signup plan/конфигурация/цена, мобильное меню, без промисов/REPLACE_ME).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
7 KiB
JavaScript
82 lines
7 KiB
JavaScript
import { chromium } from '@playwright/test'
|
||
import { mkdirSync, writeFileSync } from 'node:fs'
|
||
import { resolve } from 'node:path'
|
||
|
||
const BASE = process.env.SITE_URL ?? 'https://test.food-market.kz'
|
||
const OUT = resolve('/home/nns/food-market', 'docs/site-fixes-2026-10-10')
|
||
mkdirSync(OUT, { recursive: true })
|
||
const results = []
|
||
const shots = []
|
||
const ok = (name, cond, detail='') => { results.push({ name, ok: !!cond, detail }); console.log(`${cond?'✓':'✗'} ${name} ${detail}`) }
|
||
|
||
const browser = await chromium.launch({ args: ['--ignore-certificate-errors'] })
|
||
|
||
// ---------- DESKTOP 1440x900 @2x ----------
|
||
const dctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 2, ignoreHTTPSErrors: true })
|
||
const d = await dctx.newPage()
|
||
async function shotD(path, file, caption) {
|
||
await d.goto(BASE + path, { waitUntil: 'networkidle' }).catch(()=>{})
|
||
await d.waitForTimeout(600)
|
||
await d.screenshot({ path: resolve(OUT, file) })
|
||
shots.push({ file, caption, kind: 'Десктоп 1440' })
|
||
}
|
||
// 1. signup business config + price 15 500
|
||
await d.goto(`${BASE}/signup?plan=business&shops=3&cashes=4&warehouses=1&employees=5`, { waitUntil:'networkidle' }).catch(()=>{})
|
||
await d.waitForTimeout(1000)
|
||
ok('signup ?plan=business: тариф «Бизнес» выбран', await d.locator('label:has(input[name="plan"]:checked):has-text("Бизнес")').count() > 0)
|
||
const cfgText = (await d.locator('div:has-text("Ваша конфигурация"):has-text("Магазинов")').last().innerText().catch(() => '')) || ''
|
||
const normCfg = cfgText.replace(/[\s\u00a0\u202f\u2009\u2007]/g, '')
|
||
ok('signup business: цена 15 500 видна', normCfg.includes('15500'))
|
||
ok('signup business: конфигурация (3 магазина) видна', /Магазинов:\s*3/.test(cfgText))
|
||
await d.screenshot({ path: resolve(OUT, 'd1-signup-business.png') }); shots.push({ file:'d1-signup-business.png', caption:'/signup?plan=business&shops=3&cashes=4&warehouses=1 — выбран «Бизнес», конфигурация и цена 15 500 ₸', kind:'Десктоп 1440' })
|
||
// 2. plan=start
|
||
await d.goto(`${BASE}/signup?plan=start`, { waitUntil:'networkidle' }).catch(()=>{}); await d.waitForTimeout(500)
|
||
ok('signup ?plan=start: «Старт» выбран', /border-brand[\s\S]{0,120}Старт/.test(await d.content()))
|
||
// 3. plan=xyz → start
|
||
await d.goto(`${BASE}/signup?plan=xyz`, { waitUntil:'networkidle' }).catch(()=>{}); await d.waitForTimeout(500)
|
||
ok('signup ?plan=xyz: откат на «Старт»', /border-brand[\s\S]{0,120}Старт/.test(await d.content()))
|
||
// desktop screenshots of key pages
|
||
for (const [p,f,c] of [['/','d-home.png','Главная'],['/pricing','d-pricing.png','Тарифы (конструктор «Бизнес»)'],['/import','d-import.png','Импорт — скриншот каталога продукта (без чужого HR-дашборда)'],['/kb/billing','d-kb-billing.png','KB: Тарифы и оплата (рендер markdown, без front-matter)'],['/blog/quick-start','d-blog-quickstart.png','Блог: быстрый старт']]) await shotD(p,f,c)
|
||
|
||
// ---------- MOBILE 390x844 @3x ----------
|
||
const mctx = await browser.newContext({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 3, ignoreHTTPSErrors: true, isMobile: true, hasTouch: true })
|
||
const m = await mctx.newPage()
|
||
await m.goto(BASE + '/', { waitUntil:'networkidle' }).catch(()=>{}); await m.waitForTimeout(700)
|
||
// no horizontal scroll
|
||
const noHScroll = await m.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth + 1)
|
||
ok('моб. главная: нет горизонтальной прокрутки', noHScroll, '')
|
||
const burger = m.locator('#mobile-menu-btn')
|
||
ok('моб. шапка: есть кнопка-гамбургер', await burger.count() > 0)
|
||
await m.screenshot({ path: resolve(OUT, 'm-home-closed.png') }); shots.push({ file:'m-home-closed.png', caption:'Моб. главная 390px — в шапке гамбургер', kind:'Моб. 390' })
|
||
await burger.click().catch(()=>{}); await m.waitForTimeout(400)
|
||
const menuVisible = await m.locator('#mobile-menu:not(.hidden)').count() > 0
|
||
ok('моб. меню открывается по гамбургеру', menuVisible)
|
||
ok('моб. меню: пункт «Тарифы» доступен', await m.locator('#mobile-menu a[href="/pricing"]').count() > 0)
|
||
await m.screenshot({ path: resolve(OUT, 'm-menu-open.png') }); shots.push({ file:'m-menu-open.png', caption:'Моб. меню открыто — все пункты + «Войти»/«Начать бесплатно»', kind:'Моб. 390' })
|
||
await m.locator('#mobile-menu a[href="/pricing"]').first().click().catch(()=>{}); await m.waitForTimeout(800)
|
||
ok('моб. тап по «Тарифы» → /pricing', m.url().includes('/pricing'))
|
||
await m.screenshot({ path: resolve(OUT, 'm-pricing.png') }); shots.push({ file:'m-pricing.png', caption:'Моб. /pricing после тапа по «Тарифы» из меню', kind:'Моб. 390' })
|
||
await m.goto(`${BASE}/signup?plan=business&shops=3&cashes=4&warehouses=1&employees=5`, { waitUntil:'networkidle' }).catch(()=>{}); await m.waitForTimeout(600)
|
||
await m.screenshot({ path: resolve(OUT, 'm-signup-business.png'), fullPage: true }); shots.push({ file:'m-signup-business.png', caption:'Моб. /signup «Бизнес» с конфигурацией и ценой', kind:'Моб. 390' })
|
||
|
||
await browser.close()
|
||
|
||
// index.html
|
||
const rows = shots.map(s => `<figure><figcaption>${s.kind}: ${s.caption}</figcaption><img src="${s.file}" loading="lazy"></figure>`).join('\n')
|
||
const checks = results.map(r => `<li class="${r.ok?'ok':'no'}">${r.ok?'✓':'✗'} ${r.name}${r.detail?` — ${r.detail}`:''}</li>`).join('\n')
|
||
const html = `<!doctype html><meta charset="utf-8"><title>Исправления сайта 2026-10-10</title>
|
||
<style>body{font:15px system-ui;margin:0;padding:24px;background:#f8fafc;color:#0f172a;max-width:1100px;margin:auto}
|
||
h1{font-size:22px} figure{margin:0 0 24px;background:#fff;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden}
|
||
figcaption{padding:8px 12px;font-size:13px;color:#475569;background:#f1f5f9} img{display:block;width:100%}
|
||
ul{background:#fff;border:1px solid #e2e8f0;border-radius:10px;padding:14px 32px} li{margin:4px 0}
|
||
.ok{color:#16a34a} .no{color:#dc2626;font-weight:700}</style>
|
||
<h1>Исправления публичного сайта test.food-market.kz — этап 1 «Сайт и выбор тарифа»</h1>
|
||
<p>Проверка после деплоя (${new Date().toISOString()}). Пройдено: ${results.filter(r=>r.ok).length}/${results.length}.</p>
|
||
<h2>Автопроверки</h2><ul>${checks}</ul>
|
||
<h2>Скриншоты</h2>${rows}`
|
||
writeFileSync(resolve(OUT, 'index.html'), html)
|
||
const failed = results.filter(r=>!r.ok)
|
||
writeFileSync(resolve(OUT, 'verify.json'), JSON.stringify({ results, shots }, null, 2))
|
||
console.log(`\n[site-verify] ${results.filter(r=>r.ok).length}/${results.length} passed → ${OUT}/index.html`)
|
||
if (failed.length) { console.log('FAILED:', failed.map(f=>f.name).join('; ')); process.exit(1) }
|