---
title: 'Kullanıcı Merkezli Sorgular'
source: 'https://academia.sh/tr/kurslar/frontend-kalitesi/kullanici-merkezli-sorgular'
course: 'Frontend Kalitesi'
language: tr
updated: '2026-08-17T18:09:22+00:00'
license: 'CC BY-SA 4.0'
---

# Kullanıcı Merkezli Sorgular

Erişilebilir rol ve erişilebilir adın sorgu ölçütüne dönüştürülmesi, aynı belgenin rol sorgusuyla ve sınıf seçicisiyle sorgulanması, iki ölçütün yeniden düzenleme ve erişilebilirlik gerilemesi karşısındaki davranışı.

Önceki ders davranışa bağlı testin uygulama ayrıntısına bağlı testten daha dayanıklı
olduğunu gösterdi, ama sınanacak ögeyi bulmak için hâlâ ağacın etiket seçimine baktı.
Sınıf adına dayanan bir seçici de aynı türden bir bağımlılıktır: her ikisi de yazarın
serbestçe değiştirebilmesi gereken şeylere dayanır.

Kullanıcı ögeyi böyle bulmaz. Fareyle çalışan kullanıcı "Kaydet" yazan bir düğme arar; ekran
okuyucuyla çalışan kullanıcı "Kaydet, düğme" diye duyurulan ögeye ulaşır. İkisinin ortak
noktası iki bilgidir: ögenin **rolü** ve **erişilebilir adı**. Bu ders o iki bilgiyi bir
sorgu ölçütüne dönüştürür.

## Ölçütün Kaynağı

Web'in Temelleri ve HTML kursunda erişilebilirlik ağacı tanımlanmıştı: tarayıcının belge
ağacından türettiği, yardımcı teknolojiye sunulan ikinci ağaç. Bu ağaçtaki her düğümün bir
rolü, çoğunun bir adı vardır. Rol ögenin ne olduğunu söyler (düğme, bağlantı, arama kutusu,
satır), ad ise hangisi olduğunu.

Testin bu iki bilgiye göre sorgu yapmasının iki sonucu vardır. Birincisi dayanıklılıktır:
sınıf adları, sarmalayıcı kaplar ve biçem mimarisi kararları rolü değiştirmez. İkincisi ve
daha önemlisi, sorgunun kendisi bir erişilebilirlik doğrulaması hâline gelir. Rolü olmayan
bir ögeyi rol sorgusu bulamaz; test kırmızıya döner ve bildirdiği şey gerçek bir kusurdur.

## Rol ve Ad Nasıl Hesaplanır

Aşağıdaki modül, hesabın öğretici bir alt kümesini kurar: yaygın ögelerin örtük rolleri, açık
`role` özniteliğinin önceliği ve ad hesabının sırası.

```js
// sorgu.mjs — erisilebilir rol ve ada gore oge secimi
export function o(tur, ozellikler = {}, ...cocuklar) {
  return { tur, ozellikler, cocuklar: cocuklar.flat() };
}

export function* dolas(dugum) {
  if (dugum === null || typeof dugum !== 'object') return;
  yield dugum;
  for (const cocuk of dugum.cocuklar ?? []) yield* dolas(cocuk);
}

function metin(dugum) {
  if (typeof dugum === 'string') return dugum;
  return (dugum.cocuklar ?? []).map(metin).join(' ').replace(/\s+/g, ' ').trim();
}

const ORTUK_ROL = {
  button: () => 'button',
  a: (oz) => (oz.href === undefined ? null : 'link'),
  table: () => 'table',
  tr: () => 'row',
  td: () => 'cell',
  th: (oz) => (oz.scope === 'row' ? 'rowheader' : 'columnheader'),
  ul: () => 'list',
  li: () => 'listitem',
  nav: () => 'navigation',
  main: () => 'main',
  h1: () => 'heading',
  h2: () => 'heading',
  h3: () => 'heading',
  form: (oz) => (oz['aria-label'] === undefined ? null : 'form'),
  input: (oz) => ({
    search: 'searchbox',
    text: 'textbox',
    number: 'spinbutton',
    checkbox: 'checkbox',
    submit: 'button',
  })[oz.type ?? 'text'] ?? null,
};

export function rol(dugum) {
  if (dugum.ozellikler?.role) return dugum.ozellikler.role;
  const kural = ORTUK_ROL[dugum.tur];
  return kural ? kural(dugum.ozellikler ?? {}) : null;
}

export function erisilebilirAd(agac, dugum) {
  const oz = dugum.ozellikler ?? {};
  if (oz['aria-labelledby']) {
    const hedef = [...dolas(agac)].find((d) => d.ozellikler?.id === oz['aria-labelledby']);
    if (hedef) return metin(hedef);
  }
  if (oz['aria-label']) return oz['aria-label'];
  if (oz.id) {
    const etiket = [...dolas(agac)].find((d) => d.tur === 'label' && d.ozellikler?.for === oz.id);
    if (etiket) return metin(etiket);
  }
  if (['button', 'a', 'h1', 'h2', 'h3', 'td', 'th'].includes(dugum.tur)) return metin(dugum);
  if (oz.title) return oz.title;
  if (oz.value !== undefined && dugum.tur === 'input' && oz.type === 'submit') return oz.value;
  return '';
}

export function roleGore(agac, aranan, { ad } = {}) {
  return [...dolas(agac)].filter(
    (d) => rol(d) === aranan && (ad === undefined || erisilebilirAd(agac, d) === ad),
  );
}

export function sinifaGore(agac, sinif) {
  return [...dolas(agac)].filter((d) => (d.ozellikler?.class ?? '').split(' ').includes(sinif));
}
```

Üç ayrıntı gerçek hesapla aynıdır. Açık `role` özniteliği örtük rolü ezer. Ad hesabı bir
öncelik dizisidir ve ilk bulunan kazanır: başka bir ögeye yapılan başvuru, doğrudan verilen
etiket dizgisi, yerel etiket ögesi, ögenin kendi içeriği, en sonda `title`. Bazı ögeler
niteliğe bağlı rol alır — bağlantı yalnız `href` taşıdığında bağlantıdır, bir bölüm ögesi
ancak adı olduğunda yer imi sayılır.

Gerçek hesap bundan geniştir: gizli ögelerin dışlanması, ad hesabının özyinelemesi ve rol
eşlemesinin tamamı standartta tanımlıdır. Buradaki alt küme, sorgunun neye dayandığını
göstermeye yeter.

## Aynı Belgeyi İki Ölçütle Sorgulamak

Sınanacak belge ölçüm giriş formudur. Üç sürüm var: başlangıç işaretlemesi, yalnız sınıf
adları değişmiş hâli ve kaydet düğmesinin rolsüz bir kaba dönüştüğü hâli.

```js
// giris-formu.mjs — olcum giris formunun uc surumu
import { o } from './sorgu.mjs';

// Surum 1: baslangic isaretlemesi
export const surum1 = () =>
  o('form', { 'aria-label': 'Olcum girisi' },
    o('h2', { class: 'form__baslik' }, 'Yeni olcum'),
    o('label', { for: 'istasyon', class: 'form__etiket' }, 'Istasyon'),
    o('input', { id: 'istasyon', type: 'text', class: 'form__girdi' }),
    o('label', { for: 'deger', class: 'form__etiket' }, 'Sicaklik'),
    o('input', { id: 'deger', type: 'number', class: 'form__girdi' }),
    o('button', { type: 'submit', class: 'dugme dugme--birincil' }, 'Kaydet'));

// Surum 2: yalniz sinif adlari ve sarmalayicilar degisti
export const surum2 = () =>
  o('form', { 'aria-label': 'Olcum girisi' },
    o('h2', { class: 'yigin__baslik' }, 'Yeni olcum'),
    o('div', { class: 'alan' },
      o('label', { for: 'istasyon', class: 'alan__ad' }, 'Istasyon'),
      o('input', { id: 'istasyon', type: 'text', class: 'alan__deger' })),
    o('div', { class: 'alan' },
      o('label', { for: 'deger', class: 'alan__ad' }, 'Sicaklik'),
      o('input', { id: 'deger', type: 'number', class: 'alan__deger' })),
    o('button', { type: 'submit', class: 'eylem eylem--birincil' }, 'Kaydet'));

// Surum 3: kaydet dugmesi rolsuz bir kaba donustu
export const surum3 = () =>
  o('form', { 'aria-label': 'Olcum girisi' },
    o('h2', { class: 'yigin__baslik' }, 'Yeni olcum'),
    o('div', { class: 'alan' },
      o('label', { for: 'istasyon', class: 'alan__ad' }, 'Istasyon'),
      o('input', { id: 'istasyon', type: 'text', class: 'alan__deger' })),
    o('div', { class: 'alan' },
      o('label', { for: 'deger', class: 'alan__ad' }, 'Sicaklik'),
      o('input', { id: 'deger', type: 'number', class: 'alan__deger' })),
    o('div', { class: 'eylem eylem--birincil', onclick: 'gonder()' }, 'Kaydet'));
```

```js
// sorgu.test.mjs
import { test, describe } from 'node:test';
import assert from 'node:assert/strict';
import { roleGore, sinifaGore, erisilebilirAd } from './sorgu.mjs';
import { surum1, surum2, surum3 } from './giris-formu.mjs';

describe('surum 1', () => {
  test('rol sorgusu kaydet dugmesini bulur', () => {
    const agac = surum1();
    assert.equal(roleGore(agac, 'button', { ad: 'Kaydet' }).length, 1);
  });

  test('sinif sorgusu ayni dugmeyi bulur', () => {
    assert.equal(sinifaGore(surum1(), 'dugme').length, 1);
  });

  test('etiketli girdinin erisilebilir adi etiket metnidir', () => {
    const agac = surum1();
    const [girdi] = roleGore(agac, 'spinbutton');
    assert.equal(erisilebilirAd(agac, girdi), 'Sicaklik');
  });
});

describe('surum 2 — sinif adlari degisti', () => {
  test('rol sorgusu hala bulur', () => {
    const agac = surum2();
    assert.equal(roleGore(agac, 'button', { ad: 'Kaydet' }).length, 1);
  });

  test('sinif sorgusu artik bulamaz', () => {
    assert.equal(sinifaGore(surum2(), 'dugme').length, 0);
  });
});

describe('surum 3 — dugme rolsuz kaba dondu', () => {
  test('sinif sorgusu bulmayi surdurur', () => {
    assert.equal(sinifaGore(surum3(), 'eylem').length, 1);
  });

  test('rol sorgusu bulamaz', () => {
    const agac = surum3();
    assert.equal(roleGore(agac, 'button', { ad: 'Kaydet' }).length, 0);
  });
});
```

```sh
node --test sorgu.test.mjs
```

```
▶ surum 1
  ✔ rol sorgusu kaydet dugmesini bulur (0.631ms)
  ✔ sinif sorgusu ayni dugmeyi bulur (0.071459ms)
  ✔ etiketli girdinin erisilebilir adi etiket metnidir (0.069417ms)
✔ surum 1 (1.141792ms)
▶ surum 2 — sinif adlari degisti
  ✔ rol sorgusu hala bulur (0.075042ms)
  ✔ sinif sorgusu artik bulamaz (0.049541ms)
✔ surum 2 — sinif adlari degisti (0.184916ms)
▶ surum 3 — dugme rolsuz kaba dondu
  ✔ sinif sorgusu bulmayi surdurur (0.077958ms)
  ✔ rol sorgusu bulamaz (0.061166ms)
✔ surum 3 — dugme rolsuz kaba dondu (0.214625ms)
ℹ tests 7
ℹ suites 3
ℹ pass 7
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 36.906584
```

İki ölçütün üç sürümdeki davranışı bir tabloda toplanabilir.

| Değişiklik | Rol ve ad sorgusu | Sınıf seçicisi |
|---|---|---|
| Sınıf adları ve sarmalayıcılar değişti | bulur | bulamaz |
| Düğme rolsüz bir kaba dönüştü | bulamaz | bulur |

İki satır iki farklı türde bilgi verir. Birinci satırda sınıf seçicisi **yanlış alarm**
üretir: davranış değişmemişken test kırmızıya döner. İkinci satırda rol sorgusu bulamaz,
çünkü **bulunacak bir düğme kalmamıştır** — o kap klavyeyle odaklanamaz, boşluk tuşuyla
çalıştırılamaz ve ekran okuyucuya düğme diye duyurulmaz. Sınıf seçicisinin bu sürümde hâlâ
"bulması" iyi bir haber değildir; testin gerçek bir gerilemeyi görmediğini gösterir.

## Ölçütlerin Önceliği

Pratikte bir sıra kurulabilir. En üstte **rol ve erişilebilir ad** vardır: her etkileşimli
öge için birincil ölçüt budur. Sonra **etiket metni** gelir; form alanlarında rolle birlikte
zaten aynı bilgiyi verir. Üçüncü sırada, etkileşimli olmayan içerik için **görünen metin**
bulunur — bir uyarı bölgesinin duyurduğu ileti gibi.

En sonda **test özniteliği** durur: yalnız sınama için eklenen bir veri özniteliği. Bunun
maliyeti, kullanıcının hiç görmediği bir sözleşme yaratmasıdır; öge erişilebilirlik ağacından
tümüyle düşse bile test yeşil kalır. Gerekçesi olan iki durum vardır: erişilebilir adı doğal
olarak bulunmayan yapısal bir kap ve aynı rol ile aynı adı taşıyan çok sayıda öge arasından
belirli birini ayırma ihtiyacı.

Sorgunun birden çok öge bulması da bir bilgidir. Aynı sayfada "Kaydet" adlı iki düğme varsa,
testin belirsizliği kullanıcının belirsizliğidir; çözüm sorguyu daraltmak değil, adları
ayırmaktır.

## Özet

- Kullanıcı ögeyi rolüyle ve adıyla tanır; sorgunun ölçütü de bu ikisi olduğunda test
  kullanıcının bakış açısını taşır.
- Örtük rol öge türünden ve niteliklerinden türer; açık `role` özniteliği onu ezer. Ad hesabı
  bir öncelik dizisidir ve ilk bulunan kazanır.
- Sınıf adları ve sarmalayıcılar değiştiğinde rol sorgusu ögeyi bulmayı sürdürdü, sınıf
  seçicisi yanlış alarm üretti.
- Düğme rolsüz bir kaba dönüştüğünde rol sorgusu bulamadı; bu, gerçek bir erişilebilirlik
  gerilemesinin testte görünmesidir.
- Ölçüt sırası rol ve ad, etiket metni, görünen metin, en sonda test özniteliğidir; test
  özniteliği kullanıcının görmediği bir sözleşme yarattığı için son çaredir.

## Sonraki Adım

Bileşen testi bileşenin kendi ağacıyla sınırlıdır. Ölçüm listesinin gerçekten sunucudan
gelmesi, adres çubuğunun güncellenmesi, formun gönderilmesi ve listenin yenilenmesi bu
sınırın dışındadır. Kullanıcının yaşadığı şey ise tam olarak bu zincirdir: parçalar tek tek
doğru çalışırken akış bir yerde kopabilir. Sonraki ders uygulamayı gerçek bir tarayıcıda
baştan sona çalıştıran testleri, akışın bir durum makinesi olarak modellenmesini ve bu
testlerin neden az sayıda tutulduğunu ele alır.
