品質検問 — 導線の点検
作品を16本、動画を90本作りました。それでも人が回らない。 調べたら、作品同士に線がほとんど引かれていませんでした。 見た目には完成しているので、数えるまで気づけません。
作品が増えれば見られる回数も増えるはずだ、と思っていました。実際は増えません。 増えたのは入口の数だけで、入った人が次に行く先が用意されていなかった。
自分の頭の中では、全部つながっています。この物語とあの地図は同じ世界観だし、 このショートの本編はあの長い動画です。作った本人には自明なので、 線を引き忘れていること自体が見えません。
作品は増えたのに回遊が起きないとき、足りないのは作品ではなく作品と作品の間です。
しかもこれは、沈黙する不具合の一種です。 リンクが無くてもエラーは出ない。ページは正常に表示される。 離脱した人は何も言わずに閉じるだけなので、苦情という形でも返ってこない。
導線は感覚の問題に見えますが、「AからBへのリンクが何本あるか」は整数です。 数えられるものは検査にできます。
大事なのは、手元のファイルではなく公開されたURLを叩くことです。 コミットし忘れや、デプロイ前の状態を、手元の検査は素通りさせます。
def get(url):
req = urllib.request.Request(url, headers={"User-Agent": UA})
return urllib.request.urlopen(req, timeout=45).read().decode("utf-8", "ignore")
top = get(BASE)
for w in WORKS: # トップ → 各作品
n = top.count('href="' + w) + top.count('href="./' + w)
print(w, n, "本")
for w in WORKS: # 各作品 → トップ
h = get(BASE + w)
back = h.count('href="../"') + h.count('href="/"') + h.count('href="index.html"')
print(w, "戻り", back, "本")
20行ほどです。実行して、出てきたのがこれでした。
| 検査 | 結果 |
|---|---|
| トップから作品へのリンク | 12ページ中 12ページ OK |
| 作品からトップへ戻れるか | 12ページ中 3ページが行き止まり |
行き止まりだったのは、いちばん検索から人が来るページ3つでした。 地図、絵本、旅の記録。どれも単体で完結して見えるので、 作っているときに「ここから他に行きたい人がいる」と考えていなかった。
検索から来た人は、そのページを見て、そのまま閉じます。 他に15本あることを、知る手段がなかった。
「トップから全作品にリンクがあるか」は誰でも確認します。抜けるのは逆方向です。 各ページに、次に行ける場所があるか。 これを全ページぶん機械で数えます。ゼロのページが行き止まりです。
直し方は難しくありません。全ページ共通の小さな固定リンクを1つ置くだけです。 ただし置いたあと、本当に押せるかを確認する必要があります。 全画面のUIを持つページでは、他の要素の下に隠れることがあるからです。
hit = pg.evaluate("""() => {
const a = document.querySelector('.homeback');
const r = a.getBoundingClientRect();
const top = document.elementFromPoint(r.x + r.width/2, r.y + r.height/2);
return top === a || a.contains(top) ? '押せる' : '覆われている';
}""")
elementFromPoint でその座標にある最前面の要素を取り、
自分自身かどうかを見ます。「表示されている」と「押せる」は別物で、
is_visible() は前者しか答えません。
動画は90本ありました。うち半分以上が短い版で、それぞれに長い本編があります。 その2本の間に線が引かれているかを数えたら、14組中7組が空でした。 さらに1組は、短い版から別の短い版に繋がっていました。
再生リストは90本に対して2本。ほぼ何もしていない状態です。 「短い版 → その長い版 → 次の短い版 → その長い版」の順で並べた再生リストを3つ作り、 公式の紐づけ欄も14組すべて埋めました。
| 効いた順 | 手段 | 備考 |
|---|---|---|
| 1 | 公式の関連動画欄 | 再生画面にリンクが出る。ただしAPIから触れず、管理画面で手入力 |
| 2 | 再生リスト | 次に何が来るかが見える。APIで一括作成できる |
| 3 | 終端の誘導カード | 短い動画はループするので過信しない |
| 4 | 説明欄の一行目 | そもそも開かれない前提で置く |
紐づけの作業中に、まったく同じタイトルの候補が2つ出ました。 過去に同じものを二重に投稿し、片方を限定公開のまま放置していたためです。
問題は、画面上でこの2つが一切区別できなかったことでした。 バッジも出ない。ここで非公開のほうを選んでも、エラーは出ません。 リンクを踏んだ人だけが見られず、こちらは気づかない。
見分けたのは、候補カードのサムネイル画像のURLでした。
i.ytimg.com/vi/(動画ID)/ という形なので、
表示されている文字が同じでも、画像の出どころには識別子が入っています。
const th = card.querySelector('.thumbnail');
const bg = th.getAttribute('style') || ''; // background-image に入っている
const id = bg.split('/vi/')[1].split('/')[0]; // ここが本当の識別子
if (id === WANT) card.click();
同じに見えるものを選ぶときは、表示ではなく識別子で選ぶ。
画面が正しく見えることは、正しいものを選んだ証拠になりません。
管理画面は保存後に正しい値を表示します。ただし表示されるのはタイトルだけで、 同名のものが2つあると、どちらが入ったかは画面から分かりません。
そこで最後に、公開ページのHTMLを取ってきて識別子を数えました。
html = get("https://www.youtube.com/shorts/" + short_id)
print(want, html.count(want)) # 正しいID → 5
print(dupe, html.count(dupe)) # 間違ったID → 0
5と0。ここで初めて「設定できた」と言えます。 公開物での検証と同じ考え方で、 確認は必ず、利用者が見るのと同じ場所で行う。
| 対象 | 直す前 | 直したあと |
|---|---|---|
| 行き止まりのページ | 3ページ | 0ページ |
| 短い版と長い版の紐づけ | 14組中 7組(うち1組は誤設定) | 14組すべて |
| 再生リスト | 2本(動画90本に対して) | 5本 |
| 同名で区別できない動画 | 2本 | 片方に識別用の接頭辞を付与 |
| トップの入口 | 分類だけ(何があるか) | 所要時間(3分/10分/30分)を追加 |
最後の行は数えた結果ではなく、数えている途中で気づいたことです。 トップページの入口が「何があるか」だけを示していて、 「自分がいま入れるか」を答えていませんでした。 初めて来た人が判断できるのは、分類ではなく所要時間のほうです。
| 症状 | 原因 | 抜け方 |
|---|---|---|
| 作品は増えるのに回遊が増えない | 入口ばかり増やして出口を作っていない | 各ページの出口をゼロ件検査する |
| リンクを置いたのに押されない | 全画面UIの下に隠れている | elementFromPoint で最前面かを判定する |
| 設定したのに反映されていない | 管理画面の表示で確認を終えている | 公開ページのHTMLで識別子を数える |
| 同名の別物を掴む | 選択UIが表示名しか出さない | サムネイルURL等に含まれるIDで選ぶ |
新しい作品を1つ作る時間で、既にある90本の導線を直せます。
今回は、後者のほうが明らかに割が良かった。
沈黙して壊れるものの分類は 品質検問 に、 検査を機械に任せる話は 自動テストの実際 に、 他の型は 新しい形の研究 に置いています。