// Круг 5: цена в плитках тарифа /signup не должна срезаться на узких экранах. // Регрессия от a0f9e0b (Chrome 154, dev-android, 360css). Ширины: 320,360,375,390,402. import { chromium } from '@playwright/test' import { mkdirSync } 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-11') mkdirSync(OUT, { recursive: true }) const WIDTHS = [320, 360, 375, 390, 402] const results = [] const ok = (n, c, d = '') => { results.push({ n, c: !!c, d }); console.log(`${c ? '✓' : '✗'} ${n} ${d}`) } const b = await chromium.launch({ args: ['--ignore-certificate-errors'] }) // проверка «плитки тарифа не срезают цену»: для каждой плитки — нет переполнения // (scrollWidth<=clientWidth) ни на самой плитке, ни на контейнере цены. async function checkTiles(p, w, label) { const data = await p.evaluate(() => { const labels = Array.from(document.querySelectorAll('form label')) .filter(l => l.querySelector('input[name="plan"]')) return labels.map(l => { const name = l.querySelector('.font-semibold')?.textContent?.trim() ?? '?' const priceBox = l.querySelector('.font-semibold')?.nextElementSibling const sumSpan = priceBox?.querySelector('span') const over = (el) => el ? el.scrollWidth - el.clientWidth : 0 return { name, labelOver: over(l), priceOver: over(priceBox), sumText: sumSpan?.textContent ?? '', sumOver: sumSpan ? Math.max(0, sumSpan.scrollWidth - l.clientWidth) : 0, } }) }) for (const t of data) { ok(`${label}@${w}: плитка «${t.name}» цена не срезана`, t.labelOver <= 1 && t.priceOver <= 1, `labelOver=${t.labelOver} priceOver=${t.priceOver} sum="${t.sumText}"`) } } for (const w of WIDTHS) { const ctx = await b.newContext({ viewport: { width: w, height: 900 }, deviceScaleFactor: 2, ignoreHTTPSErrors: true, isMobile: true, hasTouch: true, userAgent: 'Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Mobile Safari/537.36', }) const p = await ctx.newPage() const noHScroll = async (label) => { const over = await p.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth) ok(`${label}@${w}: нет горизонтальной прокрутки`, over <= 1, `overflow=${over}`) } // /signup?plan=start await p.goto(`${BASE}/signup?plan=start`, { waitUntil: 'networkidle' }); await p.waitForTimeout(600) await checkTiles(p, w, 'start') await p.screenshot({ path: resolve(OUT, `k5-signup-start-${w}.png`), fullPage: true }) await noHScroll('start') // /signup?plan=business&shops=3&cashes=5&warehouses=5&employees=10 → 18 000 ₸ await p.goto(`${BASE}/signup?plan=business&shops=3&cashes=5&warehouses=5&employees=10`, { waitUntil: 'networkidle' }); await p.waitForTimeout(700) await checkTiles(p, w, 'biz') const sum = await p.locator('span.font-bold.whitespace-nowrap').first().innerText().catch(() => '') ok(`biz@${w}: итог = 18 000 ₸/мес`, /18[\s ]000[\s ]₸.?\/.?мес/.test(sum), JSON.stringify(sum)) const sumBox = await p.locator('span.font-bold.whitespace-nowrap').first().boundingBox().catch(() => null) ok(`biz@${w}: итог одной строкой`, sumBox && sumBox.height <= 24, sumBox ? `h=${Math.round(sumBox.height)}` : 'нет') await p.screenshot({ path: resolve(OUT, `k5-signup-biz-${w}.png`), fullPage: true }) await noHScroll('biz') await ctx.close() } // главная/тарифы не сломались (одна ширина — 360) { const ctx = await b.newContext({ viewport: { width: 360, height: 900 }, deviceScaleFactor: 2, ignoreHTTPSErrors: true, isMobile: true, hasTouch: true }) const p = await ctx.newPage() await p.goto(`${BASE}/`, { waitUntil: 'networkidle' }); await p.waitForTimeout(400) const hero = await p.locator('p:has-text("Подписка от") span').first().boundingBox().catch(() => null) ok('home@360: hero цена одной строкой', hero && hero.height <= 24, hero ? `h=${Math.round(hero.height)}` : 'нет') const over = await p.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth) ok('home@360: нет горизонтальной прокрутки', over <= 1, `overflow=${over}`) await p.screenshot({ path: resolve(OUT, `k5-home-360.png`), fullPage: true }) await ctx.close() } await b.close() const pass = results.filter(r => r.c).length console.log(`\n[site-verify-5] ${pass}/${results.length} passed → ${OUT}`) process.exit(pass === results.length ? 0 : 1)