0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

contenteditable に Playwright で本文を貼ったら「画面には全部あるのに途中までしか保存されない」を防ぐ確かめ方

0
Last updated at Posted at 2026-10-24

Medium のエディタ(contenteditable)に、Playwright で記事の HTML を貼り付けて下書きを作る処理を回しています。

ある日、公開済みの記事を読み返したら、後ろ半分(手順の3〜5とチェックリスト)が丸ごと無い状態で公開されていました。貼り付けた直後の画面のスクリーンショットには、最後の段落まで写っていました。この記事は、その原因と、保存まで確かめる手順です。

起きたこと

  • 貼り付けの約6秒後に、確認が失敗したのでブラウザを閉じていた
  • 画面上は最後まで入っていたが、サーバーに保存されていたのは途中の段落まで
  • その下書きを、人が中身を最後まで見ずに公開してしまった
  • 次の週の下書きも、ほぼ同じ位置で切れていた

原因1:閉じるのが早すぎた

エディタの自動保存は、貼り付けた瞬間に全部を送るわけではありません。数秒おきに差分を送るので、送り終わる前にコンテキストを閉じると、そこまでしか残りません。画面(DOM)に入っていることと、保存されたことは別でした。

原因2:確認が「先頭だけ」で、しかも誤って落ちていた

元の確認は、本文の先頭25文字が画面にあるかだけを見ていました。

const head = bodyHtml.replace(/<[^>]+>/g, '').slice(0, 40);
if (!text.includes(head.slice(0, 25))) await fail(4, '本文が入っていない');

これには穴が2つありました。

  • エディタが ' を ’、" を “ ” に置き換える。HTML 側には &#x27; のような実体参照が残る。このため、本文は入っているのに一致せず失敗になり、すぐ閉じていた
  • 先頭が合っていても、末尾まで入ったか・保存されたかは分からない

手順1:比べる前に表記をそろえる

const norm = s => s
  .replace(/&#x27;|&#39;/g, "'").replace(/&quot;/g, '"').replace(/&amp;/g, '&')
  .replace(/[‘’]/g, "'").replace(/[“”]/g, '"')
  .replace(/\s+/g, ' ');
const plain = norm(bodyHtml.replace(/<[^>]+>/g, ' ')).trim();
const head = plain.slice(0, 30);
const tail = plain.slice(-30);   // 末尾も見る

手順2:「保存済み」の表示と末尾の両方が出るまで待つ

const editorText = () => page.evaluate(() =>
  [...document.querySelectorAll('[contenteditable="true"]')].map(e => e.innerText).join('\n'));

let saved = false;
for (let k = 0; k < 30 && !saved; k++) {          // 最大90秒
  await page.waitForTimeout(3000);
  const header = await page.evaluate(() => document.body.innerText.slice(0, 400));
  // 見出しは「DraftSaved」のように単語がつながって出る。\bSaved\b だと一致しない
  saved = /Saved/.test(header) && norm(await editorText()).includes(tail);
}
if (!saved) await fail(4, '末尾まで保存されたことを確かめられない');

実際にここで一度はまりました。保存の表示は画面上「Draft」「Saved」と並んで見えます。ところが innerText では DraftSaved とつながっていて、\bSaved\b が一度も一致しませんでした。

手順3:開き直して、末尾が残っているかを見る

保存されたかの最終確認は、ページを読み込み直した後の中身で行います。長い記事は下までスクロールしないと描画されないことがあるので、スクロールしてから読みます。

await page.waitForTimeout(5000);
await page.reload({ waitUntil: 'domcontentloaded' });
await page.waitForTimeout(9000);
for (let i = 0; i < 15; i++) { await page.mouse.wheel(0, 2500); await page.waitForTimeout(500); }
const after = norm(await editorText());
if (!after.includes(head) || !after.includes(tail)) {
  await fail(4, '開き直すと本文の先頭か末尾が無い(途中までしか保存されていない)');
}

確かめ方

  • 直した版で同じ記事を流し、開き直した後の本文が全文(8,420字)あることを確認した
  • 公開ページの確認は、RSS や「取得した HTML」だけで判断しない。どちらも途中で切れていることがあり、切れた記事と見分けられない。決まった語(各見出し・最後の段落の語)が何回出るかを数える

まとめ

  • 「画面に入った」と「保存された」は別。自動保存のエディタは、閉じるのを待たせる
  • 確認は先頭だけでなく末尾も見る。比べる前に、引用符や実体参照の表記をそろえる
  • 最終確認は開き直した後の中身で行う
  • 確認が誤って落ちて早く閉じると、それ自体が「途中保存」の原因になる。誤検知の方向も確かめておく
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?