docs(site): отчёт и скриншоты Круга 5 (плитки /signup не срезают цену, 320–402px)
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

Автопроверка 52/52: scrollWidth≤clientWidth на всех плитках тарифа × 5 ширин.
Скриншоты k5-*.png, скрипт site-fixes-verify-5.mjs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nns 2026-10-12 01:16:37 +05:00
parent ee5c7ecc56
commit e4d756e004
13 changed files with 122 additions and 0 deletions

View file

@ -250,3 +250,35 @@ iPhone 17) нашёл ещё два места, где «сумма + ₸ + /м
Остальное в прогоне владельца чисто (меню, «Назад», тарифы, выбор
Старт/Бизнес/Сеть, нет горизонтальной прокрутки на 25 страницах) — не трогал.
Юридические реквизиты не трогал. Прод-сайт не трогал. Весы — пауза в силе.
---
## Круг 5 (прогон этапа 1 в Chrome 154 на dev-android, 360css — 12.10)
Регрессия от Круга 4 (`a0f9e0b`): на /signup `whitespace-nowrap` делал цену
плитки тарифа **шире узкой колонки** (плитка ~87px, контента ~65px на 360px).
«14 000 ₸/мес» (scrollWidth 77 > 65) срезался — на экране «14 000 ₸/ме».
Исправлено (commits `a5f885c`, `ee5c7ec`; деплой только на тест, статус `OK`):
- **Плитки тарифа `/signup` — сумма и «/мес» на разных строках.** Сумма+«₸» —
одной неразрывной строкой (`whitespace-nowrap`, видна целиком), «/мес» —
отдельной строкой под суммой (меньшим кеглем). Плитки остаются в 3 колонки,
внутри суммы переноса нет, хвост не срезается.
- **Плитка «Сеть» — «по запросу» переносится по словам** (это не сумма, nowrap
ей не нужен; на 320px слово не влезало в колонку).
- Итог «Ваша конфигурация „Бизнес“» и карточки тарифов на главной — без
изменений (там полноширинные строки, запаса хватает).
**Автопроверка (Playwright, Chrome-154/Android-UA):** ширины **320, 360, 375,
390, 402** × `/signup?plan=start` и `/signup?plan=business&shops=3&cashes=5&
warehouses=5&employees=10` (итог **18 000 ₸/мес**) + главная. Для каждой плитки
проверено, что `scrollWidth ≤ clientWidth` (нет переполнения/среза): **52/52
passed**. Суммы «5 000 ₸», «10 000 ₸», «18 000 ₸» — `priceOver=0` на всех
ширинах. Итог «Бизнес» = `18 000 ₸⁠/⁠мес`, одна строка (20px). Горизонтальной
прокрутки нет.
Скриншоты (`docs/site-fixes-2026-10-11/`): `k5-signup-start-{320,360,375,390,402}.png`,
`k5-signup-biz-*.png`, `k5-home-360.png`. Скрипт: `tests/e2e/tools/site-fixes-verify-5.mjs`.
Весы — пауза в силе, не трогал. Прод-сайт и реквизиты не трогал.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

View file

@ -0,0 +1,90 @@
// Круг 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)