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?

Playwright で画面の状態を文字で判定するなら、ユーザーが入力した本文を除いてから探す

0
Last updated at Posted at 2026-10-31

Playwright でブラウザを操作して、自分の投稿を決まった時刻に送る処理を回しています。送る直前に「画面が想定どおりの状態か」を文字で確かめる検査を入れていたのですが、本文にたまたま特定の語が入った日だけ、検査が「異常」と判定して送信を止めていました。この記事は、その原因と直し方の手順です。

起きたこと

投稿画面(モーダル)には「今すぐ送る」ボタンと「予約設定」のアイコンがあります。前回の操作で予約設定が残っていると、ボタンが「予約設定」に変わり、押すと即時ではなく予約として送られます。これを防ぐため、送信前にこう判定していました。

const dialog = page.locator('[role="dialog"]').filter({ has: page.locator('[data-testid="tweetTextarea_0"]') }).first();
const btn = dialog.locator('[data-testid="tweetButton"]').first();
const label = (await btn.innerText()).trim();

// 元の検査
if (/予約|Schedule/i.test(label)
    || await dialog.locator('[data-testid="unsentButton"]').count() && /予約/.test(await dialog.innerText())) {
  console.error(`FAIL 送信ボタンが即時投稿でない(${label})。予約設定が残っている`);
  process.exit(6);
}

ある日、ログにこう出て投稿されませんでした。

入力照合 OK (123字)
FAIL 送信ボタンが即時投稿でない(ポストする)。予約設定が残っている

ボタンの文字は「ポストする」(即時投稿)なのに、予約が残っていると判定しています。

原因:判定に使った文字に、入力した本文が含まれていた

2つ目の条件が原因でした。

  • unsentButton(下書きボタン)は、予約の有無に関係なくいつもモーダルにある
  • dialog.innerText() は、モーダルの中の文字を入力欄の本文ごと返す

その日の本文には「予約番号」という語が入っていました(携帯の乗り換え手続きの話でした)。そのため、「下書きボタンがある」かつ「モーダルの文字に『予約』がある」が成り立ち、予約が残っていると誤判定しました。ほかの日に問題が出なかったのは、本文に「予約」が無かったからです。

予約の残りを防ぐための検査が、本文の内容しだいで黙って送信を止めていた、というのが今回の不具合です。

手順1:まず、空の画面で何が見えているかを確かめる

直す前に、何も入力していない状態のモーダルを読み出しました。

const r = await page.evaluate(() => {
  const d = [...document.querySelectorAll('[role="dialog"]')]
    .find(x => x.querySelector('[data-testid="tweetTextarea_0"]'));
  return {
    text: d.innerText,
    btn: d.querySelector('[data-testid="tweetButton"]').innerText,
    ids: [...new Set([...d.querySelectorAll('[data-testid]')].map(e => e.getAttribute('data-testid')))],
  };
});
console.log(JSON.stringify(r, null, 1));

結果は text: "下書き\n全員\nいまどうしてる?\n\n\n全員が返信できます\nポストする" で、「予約」はどこにもありませんでした。unsentButton は常にあることも確かめられます。画面側の文字には「予約」が無く、本文を入れたときだけ出てくる、と切り分けられました。

手順2:入力欄を取り除いた写しから文字を探す

判定に使う文字から、ユーザーが入力した部分を取り除きます。元の DOM は触らず、cloneNode(true) した写しから入力欄を消してから textContent を取ります。

const label = (await btn.innerText().catch(() => '')).trim();
// 本文の入力欄を除いた、画面側の文字だけ
const uiText = await dialog.evaluate(d => {
  const c = d.cloneNode(true);
  c.querySelectorAll('[data-testid^="tweetTextarea_0"]').forEach(e => e.remove());
  return c.textContent || '';
}).catch(() => '');

if (/予約|Schedule/i.test(label)
    || await dialog.locator('[data-testid="scheduledConfirmationPrimaryAction"]').count()
    || /予約|Will send/i.test(uiText)) {
  console.error(`FAIL 送信ボタンが即時投稿でない(${label})。予約設定が残っている`);
  process.exit(6);
}

変えた点は3つです。

  1. 本文を除いた文字(uiText)で探す。入力欄は data-testid が tweetTextarea_0 で始まる要素なので、前方一致でまとめて消します
  2. 「いつもある要素」を条件から外す。unsentButton は予約の有無を示さないので、判定に使いません
  3. 予約が残っているときにだけ出る要素や文言を条件にする(予約の確認ボタンや、画面側に出る「予約」の文字)

手順3:「止めるべきでないもの」が通ることを確かめる

検査を直したら、止めるべきでないものが通ることを必ず確かめます。弾きすぎはエラーとして表に出ず、「投稿が出ていない」という形でしか見えないからです。

送信だけしない確認モード(--check)を作っておき、問題になった本文そのもの(「予約番号」を含む)で通しました。

送信元: ... @アカウント名
入力照合 OK (123字)
CHECK OK: 送信元・重複なし・入力照合・送信ボタン有効。送信はしていない

手順4:止まった日を、あとから見えるようにする

この不具合は、ログに FAIL が1行出るだけで、何日も気づかれない作りでした。投稿ごとのログに FAIL があり、完了の印(.done)が無い日を、毎日のレポートの「要確認」に出すようにしました。

import glob, re
from pathlib import Path

for f in sorted(glob.glob("texts/*.log")):
    m = re.match(r"(\d{4}-\d{2}-\d{2})_(x|threads|bluesky)\.log$", Path(f).name)
    if not m or Path(f).with_suffix(".done").exists():
        continue
    fail = next((ln for ln in Path(f).read_text(encoding="utf-8").splitlines() if "FAIL" in ln), "")
    print(f"予定投稿が出ていない: {m.group(1)} {m.group(2)}({fail[:80]})")

まとめ

  • 画面の状態を文字で判定するときは、ユーザーが入力した本文を除いた文字だけを見る。innerText は入力欄の中身も返す
  • 「いつもある要素」を条件に入れない。その状態のときにだけ出る要素を探す
  • 検査を直したら、**止めるべきでない入力(今回の本文そのもの)**で通ることを確かめる
  • 検査で止めた日は、ログの中だけでなく、人が毎日見る場所に出す
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?