AI Agent'lar İçin Site: Semantic HTML, Accessibility Tree ve Lighthouse Agentic Browsing
Browser agent'lar sayfayı ekran görüntüsü, DOM ve accessibility tree üzerinden okur; div ile kurulmuş bir düğme orada hiç görünmez, adı olmayan bir button da ne yaptığını söylemez. Angular 22 form'unun iki sürümünü accessibility tree dökümü ve Lighthouse 13.6.0'ın Agentic Browsing kategorisiyle ölçüyor, aracın neyi yakalayıp neyi kaçırdığını ve WebMCP'nin deneysel durumunu gösteriyoruz.
Sürüm notu: Bu yazı Google'ın AI (Artificial Intelligence, yapay zekâ) optimization rehberindeki "agentic experiences" bölümünü ve Chrome for Developers'ın Haziran 2026 tarihli agent-ready toolkit yazısını esas alır; kod örnekleri Angular 22.2, Lighthouse 13.6.0 ve Chrome 156 ile denenmiştir.
Önce Tahmin Et
Bookstore'un "siparişimi takip et" sayfasında bir sipariş numarası kutusu, büyüteç ikonlu bir arama düğmesi ve tıklanınca kutuyu temizleyen "Clear" yazılı bir div var. Bir kullanıcı AI asistanına "Bookstore'daki 4711 numaralı siparişime bak" diyor ve asistanın browser agent'ı sayfayı açıyor. Sence agent hangi element'leri tıklanabilir bir şey olarak görür? Lighthouse'un yeni Agentic Browsing kategorisi bu sayfadaki sorunların hepsini yakalar mı?
Browser Agent Bir Sayfayı Nasıl Okur?
Google'ın AI optimization rehberinin "Explore agentic experiences" bölümü, agent'ları kullanıcılar adına rezervasyon yapmak veya ürün özelliklerini karşılaştırmak gibi görevleri yerine getiren otonom sistemler olarak tanımlıyor. Rehbere göre browser agent'lar bir sayfayı üç yoldan anlayabiliyor: ekran görüntülerini analiz ederek, DOM'u (Document Object Model) inceleyerek ve accessibility tree'yi okuyarak. Rehberin önerisi semantic HTML (HyperText Markup Language); gerekçesi de dikkat çekici: semantic HTML ekran okuyucuların sayfayı daha kolay ayrıştırıp gezinmesine yardım ediyor.
Bu cümle aslında GEO (Generative Engine Optimization) tarafında agent'lar için söylenebilecek en sağlam şey: yeni bir standart beklemeye gerek yok, çünkü ekran okuyucu kullanan bir insanın ihtiyaç duyduğu yapı ile bir agent'ın ihtiyaç duyduğu yapı büyük ölçüde aynı. Chrome ekibinin agent-ready toolkit yazısı da aynı şeyi başka bir cümleyle söylüyor: accessibility önce insanlar için.
Agent kavramının kendisini, tool kullanımını ve agent loop'unu AI Agent'ları ve MCP (Model Context Protocol) serisinde anlattık; bu yazı yalnızca sitenin agent'a nasıl göründüğüyle ilgileniyor.
Accessibility Tree: Agent'ın Gördüğü Sayfa
Bookstore'un sipariş takip formunun iki sürümünü Angular 22'de yazdım. Önce sık görülen hâli:
@Component({
selector: 'app-order-lookup-bad',
template: `
<div class="search">
<input type="text" placeholder="Order number" (input)="orderNumber.set($any($event.target).value)">
<button type="button" (click)="lookup()"><span class="icon-search"></span></button>
<div class="link" (click)="clear()">Clear</div>
</div>
`,
})
export class OrderLookupBad {
readonly orderNumber = signal('');
lookup(): void {}
clear(): void {}
}
Sonra semantic hâli:
@Component({
selector: 'app-order-lookup',
template: `
<form (submit)="lookup($event)">
<label for="order-number">Order number</label>
<input id="order-number" name="orderNumber" type="text" required
(input)="orderNumber.set($any($event.target).value)">
<button type="submit">Find my order</button>
</form>
<p role="status">{{ status() }}</p>
`,
})
export class OrderLookup {
readonly orderNumber = signal('');
readonly status = signal('');
lookup(event: Event): void {
event.preventDefault();
this.status.set(`Looking up order ${this.orderNumber()}`);
}
}
İki component'i @angular/platform-server ile sunucuda render edip Bookstore'un statik dosyaları arasına koydum ve Chrome'un accessibility tree'sini puppeteer-core ile döken küçük bir script yazdım:
import * as chromeLauncher from 'chrome-launcher';
import puppeteer from 'puppeteer-core';
const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless=new', '--no-first-run'] });
const browser = await puppeteer.connect({ browserURL: `http://127.0.0.1:${chrome.port}` });
const page = await browser.newPage();
for (const name of ['bad', 'good']) {
await page.goto(`http://localhost:5481/agents/${name}.html`);
const tree = await page.accessibility.snapshot({ interestingOnly: true });
const lines = [];
const walk = (node, depth) => {
lines.push(`${' '.repeat(depth)}${node.role}${node.name ? ` "${node.name}"` : ''}`);
for (const child of node.children ?? []) walk(child, depth + 1);
};
walk(tree, 0);
console.log(`--- ${name}.html\n${lines.join('\n')}`);
}
await browser.disconnect();
await chrome.kill();
--- bad.html
RootWebArea "Track your order | Bookstore"
textbox "Order number"
button
StaticText "Clear"
--- good.html
RootWebArea "Track your order | Bookstore"
form
StaticText "Order number"
textbox "Order number"
button "Find my order"
status
Baştaki ilk sorunun cevabı bu çıktıda. Kötü sürümde agent'ın gördüğü bir button var ama adı yok; ne işe yaradığını ancak ekran görüntüsünden ikonu yorumlayarak tahmin edebilir. "Clear" ise bir button bile değil: accessibility tree'de yalnızca bir StaticText. Tıklanabilir olduğunu söyleyen hiçbir şey yok. İyi sürümde ise bir form, adı olan bir textbox, ne yaptığını söyleyen bir button ve sonucun yazılacağı bir status bölgesi var.
button ile div, label ile placeholder
Farkın nedeni tarayıcının yerleşik semantiği. <button> kendiliğinden bir role'e, klavye odağına ve Enter/Space davranışına sahip; içindeki metin veya aria-label ona bir accessible name veriyor. <div (click)> ise yalnızca JavaScript'e bir event listener ekliyor; tarayıcı ve dolayısıyla accessibility tree bunun bir kontrol olduğunu bilmiyor. Angular'ın (click) binding'i bu açıdan sihirli bir şey yapmıyor.
Aynı ayrım navigasyon ile aksiyon arasında da geçerli. Başka bir sayfaya götüren her şey <a href> olmalı, sayfada bir iş yapan her şey <button>. Agent, bir link'i takip edebileceğini, bir button'a basınca ise bir şeyin değişeceğini bu ayrımdan anlıyor; Google'ın crawler'ının yalnızca href taşıyan <a> element'lerini link saymasıyla aynı mantık.
İkon düğmesine ad vermenin iki yolu var: görünür bir metin ya da aria-label="Search orders". Görünür metin hem insan hem agent için daha açık; ikon zorunluysa aria-label şart.
Input tarafında ilginç bir ayrıntı çıktıda görünüyor: kötü sürümdeki textbox da "Order number" adını almış. Tarayıcı, label yoksa placeholder'ı accessible name olarak kullanabiliyor. Bu bir çözüm değil, bir yedek: placeholder yazmaya başlayınca kayboluyor ve birçok rehber ona tek başına güvenmemeyi öneriyor. <label for> ile input'u bağlamak, adı kalıcı ve açık hâle getiriyor. Form ve type="submit" ise agent'a "bu alanlar birlikte gönderilir" bilgisini veriyor; ayrıca form'un Enter ile gönderilmesi gibi tarayıcı davranışları ücretsiz geliyor.
Lighthouse Agentic Browsing Kategorisi
Chrome ekibi 22 Haziran 2026'da agent-ready toolkit'i duyurdu; içindeki en somut araç Lighthouse'un Agentic Browsing kategorisi. Duyuruya göre kategori bilgilendirme amaçlı ve kıyaslama için değil: klasik 0–100 puanı yerine geçti/kaldı durumları ve uyarılar veriyor; kategorinin kendi açıklaması da hâlâ geliştirme aşamasında olduğunu ve değişebileceğini söylüyor. npm'deki lighthouse 13.6.0'ın kaynak koduna baktım; kategoride şu audit'ler var:
agent-accessibility-tree: axe kurallarının agent'lar için seçilmiş bir alt kümesi; aralarındabutton-name,label,link-name,document-titleve bir dizi ARIA (Accessible Rich Internet Applications) kuralı var.cumulative-layout-shift: render sonrası element'lerin yerinden oynaması, yani CLS (Cumulative Layout Shift). Agent tıklamak üzereyken kayan bir düğme, yanlış yere tıklamak demek.llms-txt: serinin beşinci yazısında gördüğümüz llms.txt kontrolü.webmcp-registered-tools,webmcp-form-coveragevewebmcp-schema-validity: WebMCP entegrasyonu.ard-schema:/.well-known/ard.jsonadresinde veyarel="ard"ile bildirilen bir ARD (Agentic Resource Discovery) manifest'inin geçerliliği.
Kategorinin skoru "fraction" modunda: uygulanabilir audit'lerden kaçının geçtiği.
Bookstore Formunu Lighthouse ile Ölçmek
İki sayfayı yerel Chrome 156 üzerinde yalnızca bu kategoriyle çalıştırdım:
npx lighthouse http://localhost:5481/agents/bad.html --only-categories=agentic-browsing --output=json --output-path=agentic-bad.json
JSON (JavaScript Object Notation) raporlarının önemli satırlarını kısa bir script'le çıkardım; sonuçlar:
agentic-bad.json category score 0.67
agent-accessibility-tree score=0 mode=binary
Buttons must have discernible text: body > app-order-lookup-bad > div.search > button
webmcp-form-coverage score=None mode=notApplicable
webmcp-registered-tools score=None mode=notApplicable
webmcp-schema-validity score=None mode=notApplicable
cumulative-layout-shift score=1 mode=numeric 0
llms-txt score=1 mode=binary
ard-schema score=None mode=notApplicable
agentic-good.json category score 1
agent-accessibility-tree score=1 mode=binary All audits passed
webmcp-form-coverage score=None mode=notApplicable
webmcp-registered-tools score=None mode=notApplicable
webmcp-schema-validity score=None mode=notApplicable
cumulative-layout-shift score=1 mode=numeric 0
llms-txt score=1 mode=binary
ard-schema score=None mode=notApplicable
Baştaki ikinci sorunun cevabı: hayır, hepsini yakalamıyor. Lighthouse adı olmayan button'u buldu. Ama "Clear" yazan div hakkında hiçbir şey söylemedi, çünkü accessibility tree'de bir kontrol olarak hiç yok; var olmayan bir element'in adının eksik olduğunu söyleyemez. Placeholder ile adlandırılmış input da tarayıcı ona bir ad verdiği için geçti. İlk denememde, ikon düğmesi yerine yalnızca div kullanan sürüm bu audit'ten tamamen geçmişti. Ders net: yeşil bir Lighthouse sonucu, agent'ın sayfayı kullanabildiğinin kanıtı değil; yalnızca tarayıcıya bildirdiğin yapının tutarlı olduğunun kanıtı.
WebMCP ve ARD: Deneysel Uçlar
WebMCP, sitelerin mevcut sayfalar üzerinden AI agent'larına yapılandırılmış tool'lar sunmasını sağlayan önerilen bir standart. Chrome ekibinin yazısına göre denemek için Chrome Canary'de chrome://flags/#enable-webmcp-testing flag'ini açmak gerekiyor. Lighthouse'un kaynak koduna göre WebMCP audit'leri tarayıcıda navigator.modelContext yoksa "uygulanamaz" sayılıyor; bu yüzden kararlı Chrome 156 üzerindeki koşumuzda üçü de notApplicable çıktı. Form coverage audit'i, form'ların WebMCP'nin declarative tool name ve description annotation'larını taşıyıp taşımadığına bakıyor. Model Context Protocol'ün kendisini AI Agent'ları ve MCP serisinin "Model Context Protocol Nedir?" yazısında anlattık; WebMCP'nin tarayıcı tarafı ise deneysel, bu yüzden burada kod vermiyorum.
ARD audit'i de Bookstore'da notApplicable; sitede bir ARD manifest'i yok. Bu da yeni ve henüz yaygın olmayan bir öneri. Her iki alan için önerim aynı: semantic HTML ve erişilebilirlik bugün herkes için işe yarıyor; WebMCP ve ARD'yi izle, ama standartlaşmadan production'a koyma.
İlk HTML'de İçerik ve Kararlı Layout
Agent'ların sayfayı nasıl render ettiği operatöre göre değişiyor ve bu konuda resmi, ayrıntılı bir doküman yok. Güvenli varsayım, serinin önceki yazılarındaki ile aynı: ana içerik ve form'lar sunucudan gelen ilk HTML'de olsun. Yukarıdaki iki sayfa da sunucuda render edildi; accessibility tree'yi JavaScript çalışmadan önce de taşıyorlar. Angular'da bunun yolu SSR (Server-Side Rendering) veya prerender; ayrıntıları Teknik SEO (Search Engine Optimization) serisinin "JavaScript SEO" ve "Angular SSR ile SEO" yazılarında.
CLS tarafında Bookstore'un iki sayfası da 0 aldı, çünkü statik ve görselsiz. Gerçek sayfalarda boyutu verilmemiş görseller, sonradan yüklenen banner'lar ve font değişimleri layout'u kaydırır. Bunların teknik iyileştirmesi ilerideki Web Performansı serisinin konusu.
Üretimde Dikkat
Agent hazırlığını accessibility işinin içinde yürüt. Ayrı bir "AI uyumluluk" projesi açmak yerine mevcut a11y (accessibility) test'lerine accessibility tree kontrollerini ekle; ilerideki Accessibility serisinde bu test'leri ayrıntılı ele alacağız.
Lighthouse'u CI'da (Continuous Integration) kritik akışlar için çalıştır: sipariş takibi, arama, sepete ekleme. Ama sonucu bir kalite kapısı değil, bir uyarı sistemi olarak kullan; kategori kendini bilgilendirme amaçlı ve değişebilir olarak tanımlıyor.
Gerçek bir agent ile elle dene. Lighthouse'un göremediğini, yani div'lerle kurulmuş kontrolleri, en hızlı bir agent'ı veya bir ekran okuyucuyu sayfada dolaştırarak bulursun.
Sık Düşülen Tuzaklar
Yeşil Lighthouse sonucunu "agent dostu" sanmak. Audit yalnızca bildirilmiş yapıyı denetler; div ile kurulmuş bir düğme orada hiç görünmeyebilir.
Placeholder'ı label yerine kullanmak. Tarayıcının yedek olarak ad vermesi kalıcı bir çözüm değil.
ARIA ile her şeyi düzeltmeye çalışmak. <div role="button" tabindex="0"> mümkün ama klavye davranışını da elle yazmanı gerektirir; doğru element zaten <button>.
Deneysel standartları production'a erken almak. WebMCP bugün kararlı Chrome'da bir flag arkasında.
Küçük Bir Deneme
★☆☆ Kendi sitenin en önemli form'unu aç ve Chrome DevTools'un Accessibility panelinde her kontrolün role ve name değerlerine bak. Adı olmayan veya role'ü olmayan bir kontrol var mı?
★★☆ Kötü sürümdeki ikon düğmesine aria-label ekle ve "Clear" div'ini <button type="button">'a çevir. Accessibility tree dökümünü ve Lighthouse sonucunu tekrar al.
★★★ Lighthouse'u Node.js API'siyle (Application Programming Interface) bir test'ten çağır: agent-accessibility-tree başarısız olursa test kırılsın, diğer audit'ler yalnızca uyarı olarak loglansın. Yalnızca yerel sunucuna karşı çalıştır.
Sitenin bot'lar, cevap motorları ve agent'lar için nasıl göründüğünü konuştuk. Serinin son yazısında hepsinin sonucunu ölçmeye çalışacağız: Search Console, Bing AI Performance, referral trafiği ve log'lar bize gerçekte ne söyleyebiliyor?