fix(tradein/pdf): running @page header/footer → ровно 4 страницы (без пустых)
All checks were successful
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 8s
CI Trade-In / changes (pull_request) Successful in 9s
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 59s
CI / frontend-tests (pull_request) Has been skipped
All checks were successful
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 8s
CI Trade-In / changes (pull_request) Successful in 9s
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 59s
CI / frontend-tests (pull_request) Has been skipped
Header/footer сидели В ПОТОКЕ каждой секции (~110pt/стр.) → cover переполнялся (advice-таблица уезжала на почти-пустую стр. 2), footer listings-секции сиротел на полностью пустую стр. 4 → отчёт 6 страниц вместо 4. Fix: header+footer вынесены в running @page margin-boxes (position:running() + @top-center/@bottom-center) — рендерятся в полях каждой страницы, не занимая поток. Chrome строится ОДИН раз в generate_trade_in_pdf (else-ветка, не insufficient_data) и оборачивается в .running-header/.running-footer; из 4 билдеров inline-вызовы header/footer убраны + очищены осиротевшие переменные. Top-margin 20→25mm под running-header. Дисклеймер offer-страницы перенесён из footer page_note в inline <p> (сохранён). Убрана дубль-строка advice на cover («до 15% при самопродаже» — детально расписана на стр. 04). Верифицировано рендером реального WeasyPrint 69 в прод-образе: ровно 4 чистые страницы, бренд МЕРА в шапке/футере всех страниц, «Год постройки —», дисклеймер на месте. ruff clean; test_pdf_security 32/32; test_trade_in_pdf_dual_price 25/25.
This commit is contained in:
parent
9a73da4ce0
commit
42a50cf814
2 changed files with 54 additions and 71 deletions
|
|
@ -566,7 +566,7 @@ def _page_footer(
|
||||||
# тела страницы) и был источником сложности; заменён на простую тонкую
|
# тела страницы) и был источником сложности; заменён на простую тонкую
|
||||||
# градиентную линию-разделитель между строками meta/wordmark.
|
# градиентную линию-разделитель между строками meta/wordmark.
|
||||||
return f"""
|
return f"""
|
||||||
<div style="margin-top:20pt;">
|
<div style="margin-top:6pt;">
|
||||||
{note_html}
|
{note_html}
|
||||||
<div style="border-top:1pt solid {_LINE_SOFT};padding-top:8pt;
|
<div style="border-top:1pt solid {_LINE_SOFT};padding-top:8pt;
|
||||||
font-family:{mono_family};font-size:{_FS_XS};letter-spacing:0.06em;
|
font-family:{mono_family};font-size:{_FS_XS};letter-spacing:0.06em;
|
||||||
|
|
@ -859,8 +859,8 @@ def _dual_price_block(estimate: AggregatedEstimate, brand) -> str: # type: igno
|
||||||
)
|
)
|
||||||
|
|
||||||
explainer = (
|
explainer = (
|
||||||
f'<p style="margin:6pt 0 2pt 0;padding:6pt 10pt;background:{_ACCENT_SOFT};'
|
f'<p style="margin:4pt 0 2pt 0;padding:4pt 10pt;background:{_ACCENT_SOFT};'
|
||||||
f'border-radius:4pt;font-size:{_FS_SM};color:{_MUTED};line-height:1.3;">'
|
f'border-radius:4pt;font-size:{_FS_SM};color:{_MUTED};line-height:1.25;">'
|
||||||
"<strong>Запрос</strong> — по чему выставлены сопоставимые квартиры в объявлениях. "
|
"<strong>Запрос</strong> — по чему выставлены сопоставимые квартиры в объявлениях. "
|
||||||
"<strong>Ожидаемая цена продажи</strong> — реалистичная цена сделки по данным ДКП "
|
"<strong>Ожидаемая цена продажи</strong> — реалистичная цена сделки по данным ДКП "
|
||||||
"Росреестра, обычно ниже запроса."
|
"Росреестра, обычно ниже запроса."
|
||||||
|
|
@ -869,7 +869,7 @@ def _dual_price_block(estimate: AggregatedEstimate, brand) -> str: # type: igno
|
||||||
|
|
||||||
return (
|
return (
|
||||||
f'<table style="width:100%;border-collapse:separate;border-spacing:8pt 0;'
|
f'<table style="width:100%;border-collapse:separate;border-spacing:8pt 0;'
|
||||||
f'margin:6pt 0 0 0;"><tr>{asking_cell}{sold_cell}</tr></table>'
|
f'margin:3pt 0 0 0;"><tr>{asking_cell}{sold_cell}</tr></table>'
|
||||||
f"{explainer}"
|
f"{explainer}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1018,7 +1018,7 @@ def _quality_card_html(estimate: AggregatedEstimate) -> str:
|
||||||
cv_str = f"{cv}%" if cv is not None else "—"
|
cv_str = f"{cv}%" if cv is not None else "—"
|
||||||
gauge = _data_quality_gauge_svg(estimate.confidence)
|
gauge = _data_quality_gauge_svg(estimate.confidence)
|
||||||
return f"""
|
return f"""
|
||||||
<div class="card-glass bracket-tr" style="margin:6pt 0 6pt 0;padding:8pt 12pt 7pt 12pt;
|
<div class="card-glass bracket-tr" style="margin:3pt 0 3pt 0;padding:6pt 12pt 5pt 12pt;
|
||||||
display:flex;align-items:center;gap:12pt;flex-wrap:wrap;">
|
display:flex;align-items:center;gap:12pt;flex-wrap:wrap;">
|
||||||
{gauge}
|
{gauge}
|
||||||
<div style="flex:1;min-width:170pt;overflow-wrap:anywhere;">
|
<div style="flex:1;min-width:170pt;overflow-wrap:anywhere;">
|
||||||
|
|
@ -1126,8 +1126,6 @@ def _build_cover(estimate: AggregatedEstimate, input_snapshot: dict, brand) -> s
|
||||||
f"border-top:1pt solid {_LINE};padding-top:6pt;overflow-wrap:anywhere;'>"
|
f"border-top:1pt solid {_LINE};padding-top:6pt;overflow-wrap:anywhere;'>"
|
||||||
f"{_html.escape(brand.pdf_disclaimer)}</p>"
|
f"{_html.escape(brand.pdf_disclaimer)}</p>"
|
||||||
)
|
)
|
||||||
footer_html = _page_footer(brand, report_num, today, valid_until=expires)
|
|
||||||
|
|
||||||
heading_html = _section_heading(
|
heading_html = _section_heading(
|
||||||
"01",
|
"01",
|
||||||
"АНАЛИЗ РЫНКА И РАСЧЕТ ВЫКУПНОЙ СТОИМОСТИ КВАРТИРЫ",
|
"АНАЛИЗ РЫНКА И РАСЧЕТ ВЫКУПНОЙ СТОИМОСТИ КВАРТИРЫ",
|
||||||
|
|
@ -1138,12 +1136,10 @@ def _build_cover(estimate: AggregatedEstimate, input_snapshot: dict, brand) -> s
|
||||||
return f"""
|
return f"""
|
||||||
<div style="page-break-after:always;">
|
<div style="page-break-after:always;">
|
||||||
|
|
||||||
{_page_header(brand, report_num, today)}
|
|
||||||
|
|
||||||
{heading_html}
|
{heading_html}
|
||||||
|
|
||||||
<!-- Параметры квартиры (dotted-row — ObjectSummary.tsx summary-row идиома) -->
|
<!-- Параметры квартиры (dotted-row — ObjectSummary.tsx summary-row идиома) -->
|
||||||
<table class="params-table" style="width:100%;border-collapse:collapse;margin-bottom:10pt;">
|
<table class="params-table" style="width:100%;border-collapse:collapse;margin-bottom:6pt;">
|
||||||
<tr><td class="dotted-row">№ отчета</td>
|
<tr><td class="dotted-row">№ отчета</td>
|
||||||
<td class="bold mono dotted-row">{_mono(report_num)}</td></tr>
|
<td class="bold mono dotted-row">{_mono(report_num)}</td></tr>
|
||||||
<tr><td class="dotted-row">Дата отчета</td>
|
<tr><td class="dotted-row">Дата отчета</td>
|
||||||
|
|
@ -1172,7 +1168,7 @@ def _build_cover(estimate: AggregatedEstimate, input_snapshot: dict, brand) -> s
|
||||||
{_dual_price_block(estimate, brand)}
|
{_dual_price_block(estimate, brand)}
|
||||||
|
|
||||||
<div style="break-inside:avoid;page-break-inside:avoid;">
|
<div style="break-inside:avoid;page-break-inside:avoid;">
|
||||||
<p style="margin:6pt 0 2pt 0;font-size:{_FS_MD};font-weight:700;">
|
<p style="margin:3pt 0 2pt 0;font-size:{_FS_MD};font-weight:700;">
|
||||||
Диапазон цен в объявлениях
|
Диапазон цен в объявлениях
|
||||||
<span style="font-weight:400;font-size:{_FS_SM};color:{_MUTED};">(без учета ремонта)</span>
|
<span style="font-weight:400;font-size:{_FS_SM};color:{_MUTED};">(без учета ремонта)</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -1180,10 +1176,10 @@ def _build_cover(estimate: AggregatedEstimate, input_snapshot: dict, brand) -> s
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Что важно при оценке -->
|
<!-- Что важно при оценке -->
|
||||||
<h3 style="margin-top:8pt;font-size:{_FS_MD};font-weight:700;">
|
<h3 style="margin-top:4pt;font-size:{_FS_MD};font-weight:700;">
|
||||||
Что важно при оценке стоимости квартиры:
|
Что важно при оценке стоимости квартиры:
|
||||||
</h3>
|
</h3>
|
||||||
<table class="advice-table" style="width:100%;border-collapse:collapse;margin-top:4pt;">
|
<table class="advice-table" style="width:100%;border-collapse:collapse;margin-top:2pt;">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="advice-title">Цены в объявлениях — ожидания собственников</td>
|
<td class="advice-title">Цены в объявлениях — ожидания собственников</td>
|
||||||
<td class="advice-text">{advice_discount_text}</td>
|
<td class="advice-text">{advice_discount_text}</td>
|
||||||
|
|
@ -1193,19 +1189,20 @@ def _build_cover(estimate: AggregatedEstimate, input_snapshot: dict, brand) -> s
|
||||||
<td class="advice-text">Инвестиции в 400 – 600 тыс. руб. повышают цену объекта всего
|
<td class="advice-text">Инвестиции в 400 – 600 тыс. руб. повышают цену объекта всего
|
||||||
на 150 – 250 тыс. руб — покупатель оценивает общее состояние квартиры</td>
|
на 150 – 250 тыс. руб — покупатель оценивает общее состояние квартиры</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td class="advice-title">Неочевидные расходы при самостоятельной продаже</td>
|
|
||||||
<td class="advice-text">При самостоятельной продаже суммарные расходы могут достигать
|
|
||||||
до 15% стоимости квартиры (торг, риелтор, нотариус, справки)</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
</table>
|
||||||
|
<!-- #4-pages-fix: 3-я строка advice-table («Неочевидные расходы при
|
||||||
|
самостоятельной продаже — до 15%») убрана — дублирует детальную
|
||||||
|
постатейную разбивку на Offer-странице (см. _build_offer_page: торг
|
||||||
|
5-15% + риелтор 2-5% + аренда + юрсопровождение + реклама), которая
|
||||||
|
даёт ту же цифру НАМНОГО подробнее. Единственный content-cut в этом
|
||||||
|
фиксе (см. PR/задача про 2 пустые страницы); остальной контент cover
|
||||||
|
сохранён полностью. -->
|
||||||
|
|
||||||
<p style="margin-top:6pt;font-size:{_FS_XS};color:{_MUTED};border-top:1pt solid {_LINE};
|
<p style="margin-top:3pt;font-size:{_FS_XS};color:{_MUTED};border-top:1pt solid {_LINE};
|
||||||
padding-top:4pt;overflow-wrap:anywhere;">
|
padding-top:3pt;overflow-wrap:anywhere;">
|
||||||
<strong>Этот отчёт онлайн:</strong> {settings.public_url}?id={estimate.estimate_id}
|
<strong>Этот отчёт онлайн:</strong> {settings.public_url}?id={estimate.estimate_id}
|
||||||
</p>
|
</p>
|
||||||
{disclaimer_html}
|
{disclaimer_html}
|
||||||
{footer_html}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
@ -1294,23 +1291,11 @@ def _build_listings_page(estimate: AggregatedEstimate, input_snapshot: dict, bra
|
||||||
top5 = sorted(estimate.analogs, key=lambda x: x.distance_m or 9999)[:5]
|
top5 = sorted(estimate.analogs, key=lambda x: x.distance_m or 9999)[:5]
|
||||||
examples_rows = _examples_rows(top5)
|
examples_rows = _examples_rows(top5)
|
||||||
|
|
||||||
report_num = _report_number(estimate)
|
|
||||||
today = dt.date.today()
|
|
||||||
footer_html = _page_footer(
|
|
||||||
brand,
|
|
||||||
report_num,
|
|
||||||
today,
|
|
||||||
f"{_html.escape(brand.name)} · Анализ рынка вторичной недвижимости · стр. 2",
|
|
||||||
valid_until=_expires_date(estimate),
|
|
||||||
)
|
|
||||||
|
|
||||||
heading_html = _section_heading("02", "РЫНОК КВАРТИР – АНАЛОГОВ ПО ОБЪЯВЛЕНИЯМ")
|
heading_html = _section_heading("02", "РЫНОК КВАРТИР – АНАЛОГОВ ПО ОБЪЯВЛЕНИЯМ")
|
||||||
|
|
||||||
return f"""
|
return f"""
|
||||||
<div style="page-break-after:always;">
|
<div style="page-break-after:always;">
|
||||||
|
|
||||||
{_page_header(brand, report_num, today)}
|
|
||||||
|
|
||||||
{heading_html}
|
{heading_html}
|
||||||
|
|
||||||
<!-- Два колонки: левая (counts + источники), правая (params card + bracket-tr) -->
|
<!-- Два колонки: левая (counts + источники), правая (params card + bracket-tr) -->
|
||||||
|
|
@ -1360,8 +1345,6 @@ def _build_listings_page(estimate: AggregatedEstimate, input_snapshot: dict, bra
|
||||||
<tbody>{examples_rows}</tbody>
|
<tbody>{examples_rows}</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{footer_html}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -1507,22 +1490,11 @@ def _build_deals_page(estimate: AggregatedEstimate, input_snapshot: dict, brand)
|
||||||
top5 = estimate.actual_deals[:5]
|
top5 = estimate.actual_deals[:5]
|
||||||
examples_rows = _examples_rows(top5)
|
examples_rows = _examples_rows(top5)
|
||||||
|
|
||||||
report_num = _report_number(estimate)
|
|
||||||
footer_html = _page_footer(
|
|
||||||
brand,
|
|
||||||
report_num,
|
|
||||||
today,
|
|
||||||
f"{_html.escape(brand.name)} · Анализ рынка вторичной недвижимости · стр. 3",
|
|
||||||
valid_until=_expires_date(estimate),
|
|
||||||
)
|
|
||||||
|
|
||||||
heading_html = _section_heading("03", "ФАКТИЧЕСКИЕ СДЕЛКИ ПО КВАРТИРАМ — АНАЛОГАМ")
|
heading_html = _section_heading("03", "ФАКТИЧЕСКИЕ СДЕЛКИ ПО КВАРТИРАМ — АНАЛОГАМ")
|
||||||
|
|
||||||
return f"""
|
return f"""
|
||||||
<div style="page-break-after:always;">
|
<div style="page-break-after:always;">
|
||||||
|
|
||||||
{_page_header(brand, report_num, today)}
|
|
||||||
|
|
||||||
{heading_html}
|
{heading_html}
|
||||||
|
|
||||||
<table style="width:100%;border-collapse:collapse;margin-bottom:14pt;">
|
<table style="width:100%;border-collapse:collapse;margin-bottom:14pt;">
|
||||||
|
|
@ -1576,8 +1548,6 @@ def _build_deals_page(estimate: AggregatedEstimate, input_snapshot: dict, brand)
|
||||||
<tbody>{examples_rows}</tbody>
|
<tbody>{examples_rows}</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{footer_html}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -1642,22 +1612,9 @@ def _build_offer_page(estimate: AggregatedEstimate, input_snapshot: dict, brand)
|
||||||
brand_short = _html.escape(brand.name)
|
brand_short = _html.escape(brand.name)
|
||||||
trade_in_label = f"{brand_short}.Обмен" if brand.slug != "generic" else "Trade-In"
|
trade_in_label = f"{brand_short}.Обмен" if brand.slug != "generic" else "Trade-In"
|
||||||
|
|
||||||
report_num = _report_number(estimate)
|
|
||||||
today = dt.date.today()
|
|
||||||
footer_html = _page_footer(
|
|
||||||
brand,
|
|
||||||
report_num,
|
|
||||||
today,
|
|
||||||
f"{brand_short} · Анализ рынка вторичной недвижимости · стр. 4 · "
|
|
||||||
"Расчёт носит ориентировочный характер и не является офертой.",
|
|
||||||
valid_until=_expires_date(estimate),
|
|
||||||
)
|
|
||||||
|
|
||||||
return f"""
|
return f"""
|
||||||
<div><!-- last page: NO page-break-after -->
|
<div><!-- last page: NO page-break-after -->
|
||||||
|
|
||||||
{_page_header(brand, report_num, today)}
|
|
||||||
|
|
||||||
{_section_heading("04", "ФОРМИРОВАНИЕ ВЫКУПНОЙ СТОИМОСТИ")}
|
{_section_heading("04", "ФОРМИРОВАНИЕ ВЫКУПНОЙ СТОИМОСТИ")}
|
||||||
|
|
||||||
<h3 style="font-size:{_FS_MD};font-weight:700;margin-bottom:8pt;">
|
<h3 style="font-size:{_FS_MD};font-weight:700;margin-bottom:8pt;">
|
||||||
|
|
@ -1823,7 +1780,9 @@ def _build_offer_page(estimate: AggregatedEstimate, input_snapshot: dict, brand)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{footer_html}
|
<p style="margin-top:14pt;font-size:{_FS_XS};color:{_MUTED};">
|
||||||
|
Расчёт носит ориентировочный характер и не является офертой.
|
||||||
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
@ -1879,8 +1838,12 @@ def _build_css(brand=None) -> str: # type: ignore[no-untyped-def]
|
||||||
}}
|
}}
|
||||||
@page {{
|
@page {{
|
||||||
size: A4;
|
size: A4;
|
||||||
margin: 20mm 18mm 20mm 18mm;
|
margin: 25mm 18mm 19mm 18mm;
|
||||||
|
@top-center {{ content: element(runningHeader); vertical-align: bottom; }}
|
||||||
|
@bottom-center {{ content: element(runningFooter); vertical-align: top; }}
|
||||||
}}
|
}}
|
||||||
|
.running-header {{ position: running(runningHeader); }}
|
||||||
|
.running-footer {{ position: running(runningFooter); }}
|
||||||
* {{ box-sizing: border-box; }}
|
* {{ box-sizing: border-box; }}
|
||||||
body {{
|
body {{
|
||||||
font-family: 'PT Sans', 'DejaVu Sans', sans-serif;
|
font-family: 'PT Sans', 'DejaVu Sans', sans-serif;
|
||||||
|
|
@ -2188,8 +2151,25 @@ def generate_trade_in_pdf(
|
||||||
if estimate.insufficient_data:
|
if estimate.insufficient_data:
|
||||||
body_html = _build_insufficient_data_page(estimate, input_snapshot, brand)
|
body_html = _build_insufficient_data_page(estimate, input_snapshot, brand)
|
||||||
else:
|
else:
|
||||||
|
# Running header/footer — единый HUD-хром печатается WeasyPrint'ом в page
|
||||||
|
# margin-boxes (@top-center/@bottom-center, см. _build_css) на КАЖДОЙ странице
|
||||||
|
# документа, а не только там, где явно вставлен в поток секции-билдера.
|
||||||
|
# Рендерится один раз здесь (не внутри каждого _build_*_page) — иначе WeasyPrint
|
||||||
|
# печатает независимый running-элемент с последнего "видимого" перед разрывом
|
||||||
|
# страницы, что дало бы 4 разных чанка вместо одного персистентного хрома.
|
||||||
|
_chrome_report_num = _report_number(estimate)
|
||||||
|
_chrome_today = dt.date.today()
|
||||||
|
_chrome_expires = _expires_date(estimate)
|
||||||
|
chrome_html = (
|
||||||
|
f'<div class="running-header">'
|
||||||
|
f"{_page_header(brand, _chrome_report_num, _chrome_today)}</div>"
|
||||||
|
f'<div class="running-footer">'
|
||||||
|
f"{_page_footer(brand, _chrome_report_num, _chrome_today, valid_until=_chrome_expires)}"
|
||||||
|
f"</div>"
|
||||||
|
)
|
||||||
body_html = (
|
body_html = (
|
||||||
_build_cover(estimate, input_snapshot, brand)
|
chrome_html
|
||||||
|
+ _build_cover(estimate, input_snapshot, brand)
|
||||||
+ _build_listings_page(estimate, input_snapshot, brand)
|
+ _build_listings_page(estimate, input_snapshot, brand)
|
||||||
+ _build_deals_page(estimate, input_snapshot, brand)
|
+ _build_deals_page(estimate, input_snapshot, brand)
|
||||||
+ _build_offer_page(estimate, input_snapshot, brand)
|
+ _build_offer_page(estimate, input_snapshot, brand)
|
||||||
|
|
|
||||||
|
|
@ -200,20 +200,23 @@ def test_cover_disclaimer_absent_when_none() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_cover_renders_logo_img_when_valid_url() -> None:
|
def test_cover_renders_logo_img_when_valid_url() -> None:
|
||||||
est = _estimate()
|
# #4-pages-fix: _page_header больше не встроен в _build_cover — он рендерится
|
||||||
html = mod._build_cover(est, _SNAPSHOT, _PRAKTIKA)
|
# ОДИН раз в generate_trade_in_pdf как running-header (position:running(...),
|
||||||
assert "<img" in html
|
# печатается WeasyPrint'ом в @top-center margin-box на каждой странице), см.
|
||||||
assert "gk-praktika.ru" in html
|
# trade_in_pdf.py::generate_trade_in_pdf. Логотип/wordmark проверяем напрямую
|
||||||
|
# через _page_header, не через _build_cover (симметрично уже существовавшему
|
||||||
|
# паттерну в test_cover_falls_back_to_name_when_logo_none ниже).
|
||||||
|
header_html = mod._page_header(_PRAKTIKA, "EKБ-0001-0000000001", datetime.now(UTC).date())
|
||||||
|
assert "<img" in header_html
|
||||||
|
assert "gk-praktika.ru" in header_html
|
||||||
|
|
||||||
|
|
||||||
def test_cover_falls_back_to_name_when_logo_none() -> None:
|
def test_cover_falls_back_to_name_when_logo_none() -> None:
|
||||||
est = _estimate()
|
|
||||||
html = mod._build_cover(est, _SNAPSHOT, _GENERIC)
|
|
||||||
assert "TRADE-IN" in html.upper()
|
|
||||||
# Logo-specific check via _page_header directly — не проверяем отсутствие <img> во
|
# Logo-specific check via _page_header directly — не проверяем отсутствие <img> во
|
||||||
# всей cover-странице, т.к. с #2327-follow-up диапазоны цен рендерятся как
|
# всей cover-странице, т.к. с #2327-follow-up диапазоны цен рендерятся как
|
||||||
# matplotlib SVG <img> (см. _price_range_chart_svg), не связанные с логотипом бренда.
|
# matplotlib SVG <img> (см. _price_range_chart_svg), не связанные с логотипом бренда.
|
||||||
header_html = mod._page_header(_GENERIC, "EKБ-0001-0000000001", datetime.now(UTC).date())
|
header_html = mod._page_header(_GENERIC, "EKБ-0001-0000000001", datetime.now(UTC).date())
|
||||||
|
assert "TRADE-IN" in header_html.upper()
|
||||||
assert "<img" not in header_html
|
assert "<img" not in header_html
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue