food-market/tests/e2e/tools/site-fixes-verify.mjs
nns ad747bf58c
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
docs(site): отчёт и скриншоты проверки исправлений test.food-market.kz (10/10)
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>
2026-10-11 00:03:01 +05:00

82 lines
7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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) }