Files
portal/app/tests/Unit/Autopodbor/Fetch/CurlPlaywrightFetcherRenderTimeoutTest.php
T
Дмитрий aca52d130d fix(автоподбор): рендер сайта больше не теряет номера при таймауте Playwright
Шаг 2: CurlPlaywrightFetcher.site() curl'ом успешно тянул страницу с tel:-номерами,
но render() (node render-page.cjs) висел на waitUntil networkidle и падал по таймауту.
Symfony Process::run() при таймауте бросает ProcessTimedOutException — оно вылетало из
site(), SiteOpener обнулял весь сбор. Итог: 0 номеров с сайта (боевой кейс Центрофинанс,
tenant info@lkomega.ru, прогон 07.07 14:07, только сайт+карточка, ни одного code-номера).

- render()/renderFirm() ловят \Throwable -> null: падение/вис рендера НЕ фатально,
  curl-HTML с номерами 'в коде сайта' сохраняется
- render-page.cjs: networkidle -> domcontentloaded + пауза; рендер 30с+вис -> 3.7с (боевая проверка)
- сиды curlRunner/nodeRunner для офлайн-теста; регресс-тест воспроизводит потерю номеров

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:16:42 +03:00

42 lines
2.4 KiB
PHP

<?php
use App\Services\Autopodbor\Agent\Fetch\CurlPlaywrightFetcher;
/**
* Регрессия боевого бага (07.07.2026, конкурент «Центрофинанс», tenant info@lkomega.ru):
* curl успешно скачивал сайт с tel:-номерами, но рендер (node render-page.cjs) висел на
* networkidle и падал по таймауту. Symfony Process::run() при таймауте БРОСАЕТ
* ProcessTimedOutException — и оно вылетало из site(), обнуляя уже успешный curl.
* Итог: ни одного номера с сайта. Рендер обязан быть НЕ фатальным: его падение не должно
* ни ронять site()/directory(), ни терять номера, добытые curl.
*
* Сиды конструктора (curlRunner/nodeRunner) подменяют сеть и запуск node — без подпроцесса.
* nodeRunner бросает исключение = «рендер завис/упал» (в бою это ProcessTimedOutException;
* продакшн ловит \Throwable, тип не важен).
*/
function fetcherWithFailingRender(?callable $curl = null): CurlPlaywrightFetcher
{
return new CurlPlaywrightFetcher(
timeout: 1,
curlRunner: $curl ?? fn (string $url): string => '',
nodeRunner: function (string $script, string $url): ?array {
throw new \RuntimeException('render timed out');
},
);
}
it('site(): при падении рендера НЕ теряет номера, добытые curl', function () {
$curl = fn (string $url): string => '<a href="tel:88007003767">8 800</a><a href="tel:84950110359">8 495</a>';
// 8.8.8.8 — публичный IP: проходит isSafeUrl без DNS; сеть не трогаем (curl подменён).
$site = fetcherWithFailingRender($curl)->site('https://8.8.8.8/');
expect($site->rawHtml)->toContain('tel:88007003767')
->and($site->rawHtml)->toContain('tel:84950110359')
->and($site->visiblePhones)->toBe([]); // рендер упал — видимых нет, но curl цел
});
it('directory(): при падении рендера возвращает [] вместо исключения', function () {
expect(fetcherWithFailingRender()->directory('https://8.8.8.8/'))->toBe([]);
});