先月、個人開発で運用しているアプリ紹介サイトのストア導線を、手元の AI ブラウザエージェントに操作させてみました。「人気の壁紙を価格の安い順に並べて、いちばん上のものをカートに入れて」という、人間なら数秒で終わる指示です。ところがエージェントは並び替えのドロップダウンを開けず、3回に2回はそこで止まってしまいました。原因はモデルの賢さではなく、私自身が作った UI が「クリックの的」を持っていなかったことでした。
I/O 26 で発表された Gemini in Chrome の Android 展開(6月下旬・RAM 4GB 以上・en-US から段階開始)と auto browse は、こうした自動操作が一般ユーザーの手元で当たり前に走る時代の入り口だと感じています。ここでは、自分のWebアプリを「エージェントが確実に操作できる構造」へ寄せるために、私が実際に手を入れた箇所を、改修前後のコードとともに残しておきます。
auto browse は「見た目で操作するUI」でつまずく
auto browse のようなブラウザエージェントは、最終的には人間と同じ画面を見ますが、操作対象を決めるときはまずアクセシビリティツリー(ブラウザが内部で持つ意味づけられた要素の木)を読みます。ここに「これは並び替えのコントロールだ」「これはカートに入れるボタンだ」という情報が無いと、エージェントは座標とテキストだけを頼りに推測することになり、推測が外れると操作が空振りします。
私が止まった並び替えドロップダウンは、見た目だけ整えた div の集合でした。視覚的にはセレクトボックスに見えますが、アクセシビリティツリー上は「意味のない箱」です。人間は見た目で理解できても、エージェントは手がかりを得られません。これが auto browse 時代に最初に直す価値のある場所だと考えています。
操作の的を固定する — アクセシブルネームと安定したフック
まず直したのは、操作対象が「いつでも同じ名前と属性で見つかる」ようにすることです。改修前は装飾優先で、ボタンの実体がアイコンだけ、ラベルはツールチップ頼みでした。
改修前:
<!-- 見た目はカートボタンだが、エージェントから見ると無名のdiv -->
< div class = "cart-icon" onclick = " addToCart ( 123 )" >
< svg >...</ svg >
</ div >
<!-- 並び替え: ネイティブselectではなく独自実装 -->
< div class = "sort-dropdown" data-open = "false" >
< span >並び替え</ span >
< ul class = "options" >
< li onclick = " sortBy ('price-asc')" >価格が安い順</ li >
</ ul >
</ div >
改修後は、ネイティブ要素とアクセシブルネーム、そして変わらないフック用属性を与えました。
<!-- role と aria-label で「何のボタンか」を明示。data-action は安定した操作フック -->
< button
type = "button"
aria-label = "この壁紙をカートに追加"
data-action = "add-to-cart"
data-product-id = "123"
onclick = " addToCart ( 123 )" >
< svg aria-hidden = "true" >...</ svg >
< span class = "visually-hidden" >カートに追加</ span >
</ button >
<!-- 並び替えはネイティブselectに戻す。ツリー上で自動的にcomboboxとして認識される -->
< label for = "sort-order" >並び替え</ label >
< select id = "sort-order" data-action = "sort-order" onchange = " applySort ( this . value )" >
< option value = "popular" >人気順</ option >
< option value = "price-asc" >価格が安い順</ option >
< option value = "price-desc" >価格が高い順</ option >
</ select >
ポイントは3つあります。第一に、独自実装のドロップダウンよりネイティブの select の方がエージェントには圧倒的に扱いやすいこと。第二に、aria-label で人間向けの見た目とは独立して機能名を与えられること。第三に、data-action のような安定した属性を置くと、クラス名をデザイン都合で変えてもエージェントの手がかりが壊れないことです。私はデザインのリファクタで class を頻繁に変えるので、操作フックは data-action に逃がす運用に統一しました。
アクセシビリティツリーをエージェントの地図にする
個々の的を固定したら、次はページ全体の地図を整えます。エージェントは「主要な領域がどこか」をランドマークから把握します。改修前の私のページは div の入れ子だけで、ヘッダーも本文も検索も区別がありませんでした。
<!-- 改修後: landmark ロールで領域を宣言する -->
< header >
< nav aria-label = "メインナビゲーション" >...</ nav >
</ header >
< main >
< section aria-labelledby = "catalog-heading" >
< h1 id = "catalog-heading" >壁紙カタログ</ h1 >
< form role = "search" aria-label = "壁紙を検索" >
< input type = "search" name = "q" aria-label = "キーワード" />
< button type = "submit" >検索</ button >
</ form >
< ul aria-label = "検索結果" >
< li >...</ li >
</ ul >
</ section >
</ main >
main / nav / search といったランドマークがあると、エージェントは「検索フォームはここ」「結果リストはここ」と一段で辿れます。私の体感では、ここを整えてから「検索して上から3番目を開いて」のような複合指示の安定度が目に見えて上がりました。アクセシビリティ対応はこれまで「人間の支援技術のため」と捉えていましたが、auto browse 時代にはエージェントのための地図でもあると考えを改めました。
ページの意図を JSON-LD で明示する
ツリーは「操作」のための地図ですが、エージェントが「このページは何なのか」を取り違えると、そもそも操作の前提を誤ります。そこで構造化データ(JSON-LD)でページの意図を機械可読にしておきます。商品ページなら Product、記事なら Article を置くだけで、価格・在庫・名称といった判断材料を曖昧さなく渡せます。
< script type = "application/ld+json" >
{
"@context": "https://schema.org",
"@type": "Product",
"name": "夜明けのグラデーション壁紙",
"image": "https://example.com/wallpapers/123.png",
"sku": "WP-123",
"offers": {
"@type": "Offer",
"price": "250",
"priceCurrency": "JPY",
"availability": "https://schema.org/InStock"
}
}
</ script >
ここで一点注意があります。JSON-LD に書いた価格や在庫は、画面に表示している実際の値と必ず一致させてください。私は当初、キャッシュされた静的な JSON-LD と、クライアントで動的計算した表示価格がずれていて、エージェントが古い価格を読んでしまう不整合に遭遇しました。構造化データは「画面の影」ではなく「画面の正本」として扱い、同じデータソースから描画する設計にしてからずれは消えました。本番では、JSON-LD と DOM 表示が同じ値を参照しているかを検証する小さなテストを置いています。
商品ページ以外でも考え方は同じです。記事ページなら Article、アプリ紹介なら SoftwareApplication を置くと、エージェントは「これは読むページか、操作するページか」を取り違えにくくなります。私は App Store 配布アプリの紹介ページに SoftwareApplication を入れてから、エージェントが「ダウンロードリンクはどれか」を一度で特定できるようになったと感じています。構造化データはSEOのためだけのものという認識を、私自身あらためました。
状態とURLを決定的に保つ
エージェントは操作の途中で前提が変わると迷います。とくに「絞り込み条件が URL に乗らず、画面の隠れた状態にだけ存在する」設計は危険です。改修前の私のカタログは、並び替えやフィルタが JavaScript の内部変数にしか無く、エージェントが一度ページを読み直すと条件が消えていました。
改修後は、絞り込み状態をすべてクエリパラメータに反映し、URL を共有・再現可能にしました。
// 並び替え・フィルタの変更を必ずURLに反映する
function applySort ( value ) {
const url = new URL (location.href);
url.searchParams. set ( "sort" , value);
// 状態をURLに置くことで、エージェントが読み直しても条件が保持される
history. replaceState ( null , "" , url);
renderCatalog ( readStateFromUrl ());
}
function readStateFromUrl () {
const p = new URL (location.href).searchParams;
return {
sort: p. get ( "sort" ) ?? "popular" ,
category: p. get ( "category" ) ?? "all" ,
};
}
加えて、同じ操作を二度実行しても結果が壊れない冪等性も意識しました。たとえば「カートに追加」を data-product-id 単位で冪等にしておくと、エージェントが確認のために再クリックしても数量が二重に増えません。auto browse は人間より迷いなく、しかし時に同じ操作を繰り返すので、冪等性は実用上の保険になります。
破壊的操作はエージェントに自動実行させない
ここは設計思想として最も大切にしている部分です。エージェントが操作できることと、何でも自動で実行してよいことは別問題です。削除・購入確定・退会のような取り返しのつかない操作は、ワンクリックで完了させず、人間の明示的な確認を挟む設計にしています。
<!-- 破壊的操作は確認ステップを必須にする。aria-describedで意図も伝える -->
< button
type = "button"
data-action = "delete-account"
aria-describedby = "delete-warning"
onclick = " openConfirmDialog ('delete-account')" >
アカウントを削除
</ button >
< p id = "delete-warning" role = "note" >
この操作は取り消せません。確認画面で本人確認が必要です。
</ p >
確認ダイアログで二段階の意思確認を必須にしておくと、エージェントが誤って削除フローへ進んでも、最終確定の前で必ず人間に判断が戻ります。私は「エージェントが速く動けること」と「人間が最終決定権を握ること」を両立させる境界をここに置いています。実装としては、破壊的操作の data-action をリスト化し、それらは確認ダイアログ経由でしか発火しないことを単体テストで担保しています。
オンデバイスエージェントを想定した検証ハーネス
最後に、改修が効いているかをどう確かめたかです。私は次の手順で繰り返し検証しました。
ブラウザのアクセシビリティインスペクタでツリーを開き、data-action を持つ要素がすべて適切な role とアクセシブルネームを持つか目視する
主要導線(検索→絞り込み→詳細→カート)を、同じ自然言語指示で5回ずつエージェントに実行させ、完走回数を記録する
JSON-LD と画面表示の価格・在庫が一致しているかを自動テストで突き合わせる
破壊的操作の data-action が確認ダイアログ無しに発火しないことをテストで確認する
改修前は、フィルター→詳細→カートの3ステップを通す指示が5回中2回しか完走しませんでした。操作の的の固定・ランドマーク整備・URL 状態化を入れたあとは、同じ指示が5回中5回完走するようになりました。完走率という単純な指標ですが、auto browse が一般化したときに「自分のサイトでユーザーのエージェントがちゃんと用を足せるか」を測る一次近似として役立っています。AdMob 収益に直結するストア導線では、この差はそのまま取りこぼしの差になると見ています。
導入の優先順位について、私が推奨しているのは「収益や離脱に直結する導線から手を入れる」という順序です。すべてのページを一度に作り変えようとすると本番のデザインまで巻き込んで事故になりやすいので、まずは購入・申し込みのような一本の導線に絞る方が安全に進められます。私自身、いきなり全画面へ data-action を入れて表示崩れを招きかけたことがあり、いまは一導線ずつ検証しながら広げる進め方に落ち着きました。こうした段階移行であれば、エージェント対応とデザイン変更の衝突を回避しやすく、本番への影響も読みやすくなります。
次の一手としては、まず手元のいちばん重要な導線を一つ選び、その操作対象に data-action とアクセシブルネームを与えるところから始めてみてください。一画面でも整えると、エージェントの挙動の安定度の違いがすぐに体感できるはずです。