グラフ入りダッシュボードのPDF出力が、必ず500で落ちていました。
原因は ECharts の finished イベントを取り逃がしていたことだったので、備忘録として残します。
何が起きていたのか
PDFは Playwright で印刷用ページを開いて生成しています。
グラフが描き終わったら window.__chartsReady を立て、それを待ってからPDFにする作りです。
await page.waitForFunction(() => window.__chartsReady === true, { timeout: 15000 });
グラフが無いダッシュボードは成功します。
グラフが1つでもあると page.waitForFunction: Timeout 30000ms exceeded で落ちます(約37秒)。
15秒を指定しているのに30秒で落ちている点は、後ほど説明します。
animation: false だと finished が来ない
印刷ページにアニメーションは不要なので、animation: false で描いていました。
「アニメーション無しなら finished が安定する」と考えていましたが、実際は逆で、一度も発火していませんでした。
-
animation: falseだと、ECharts は描画とrendered/finishedをsetOptionの中で同期的に終わらせる -
echarts-for-reactはイベントの購読をsetOptionの後に行う
つまり、購読した時点で finished はすでに終わっています。来るはずがありません。
onChartReady は呼ばれますが、そこで購読した finished / rendered も来ませんでした。
直し方
onChartReady は購読の後に呼ばれるので、そこで「描画済み」として通知することにしました。
<ReactECharts
option={option}
onChartReady={() => notifyRendered()}
/>
通知は1チャートにつき1回だけにしています。
finished が複数回来るケースでも、カウントが実際のグラフ数を超えないようにするためです。
ついでに: waitForFunction のタイムアウト
// NG: 第2引数は arg なので timeout が効かない
await page.waitForFunction(fn, { timeout: 15000 });
// OK: タイムアウトは第3引数
await page.waitForFunction(fn, undefined, { timeout: 15000 });
15秒のつもりが既定の30秒になっていたのは、これが原因でした。
これで、グラフ2つのダッシュボードが「37秒かけて500」から「9秒で200」になりました。
最後に
- アニメーションを切っているときの
finished/renderedは、購読する前に発火済みになっていないかを見る - テストが通っていても、実ブラウザ・実サーバーで通していない機能は動いていないことがある。PDF生成のように外のプロセスをまたぐ処理は、E2Eで一度通す