docs(site): отчёт и скриншоты Круга 5 (плитки /signup не срезают цену, 320–402px)
Автопроверка 52/52: scrollWidth≤clientWidth на всех плитках тарифа × 5 ширин. Скриншоты k5-*.png, скрипт site-fixes-verify-5.mjs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
@ -250,3 +250,35 @@ iPhone 17) нашёл ещё два места, где «сумма + ₸ + /м
|
||||||
Остальное в прогоне владельца чисто (меню, «Назад», тарифы, выбор
|
Остальное в прогоне владельца чисто (меню, «Назад», тарифы, выбор
|
||||||
Старт/Бизнес/Сеть, нет горизонтальной прокрутки на 25 страницах) — не трогал.
|
Старт/Бизнес/Сеть, нет горизонтальной прокрутки на 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`.
|
||||||
|
|
||||||
|
Весы — пауза в силе, не трогал. Прод-сайт и реквизиты не трогал.
|
||||||
|
|
|
||||||
BIN
docs/site-fixes-2026-10-11/k5-home-360.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
docs/site-fixes-2026-10-11/k5-signup-biz-320.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
docs/site-fixes-2026-10-11/k5-signup-biz-360.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
docs/site-fixes-2026-10-11/k5-signup-biz-375.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
docs/site-fixes-2026-10-11/k5-signup-biz-390.png
Normal file
|
After Width: | Height: | Size: 186 KiB |
BIN
docs/site-fixes-2026-10-11/k5-signup-biz-402.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
docs/site-fixes-2026-10-11/k5-signup-start-320.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
docs/site-fixes-2026-10-11/k5-signup-start-360.png
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
docs/site-fixes-2026-10-11/k5-signup-start-375.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
docs/site-fixes-2026-10-11/k5-signup-start-390.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
docs/site-fixes-2026-10-11/k5-signup-start-402.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
90
tests/e2e/tools/site-fixes-verify-5.mjs
Normal 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)
|
||||||