---
title: 'Tarayıcı Otomasyonu'
source: 'https://academia.sh/tr/kurslar/tumlesim-testi/tarayici-otomasyonu'
course: 'Tümleşim, Sözleşme ve Uçtan Uca Test'
language: tr
updated: '2026-08-17T18:10:50+00:00'
license: 'CC BY-SA 4.0'
---

# Tarayıcı Otomasyonu

Sayfayı süren testlerin iki kararı olan seçici seçimi ile bekleme politikasının ölçülmesi; dört seçici türünün yapı değişikliği karşısındaki dayanıklılığı ve sabit uyku, örtük bekleme ile açık beklemenin düşme oranı ve bekleme bedeli.

Önceki dersin uçtan uca testleri HTTP arayüzünü sürdü: bir istek gönderildi, bir durum kodu
okundu. Üyenin gerçekte gördüğü yüzey ise bir sayfadır. Bir sayfayı süren test iki karar
vermek zorundadır ve ikisi de kod yazarken görünmez, koşumda görünür: hangi ögeye
dokunulacağı nasıl söylenir ve o ögenin hazır olması nasıl beklenir.

Bu ders gerçek bir tarayıcı sürmez. Sürülen şey sayfanın küçük bir ağaç modelidir; iki
karar da o model üzerinde gerçekten hesaplanır. Ölçülen şey aracın davranışı değil, iki
kararın kendisidir — hangi seçici türü hangi değişiklikte kırılıyor ve hangi bekleme
politikası düşme oranını ne kadar değiştiriyor.

**Seçici** sözcüğü burada CSS ile Görsel Sunum kursundaki seçiciyle aynı sözcüktür, ama iş
farklıdır: orada bir kurala hangi ögelerin uyacağı, burada bir teste hangi ögeyi süreceği
söylenir.

## Sayfanın Modeli

Ödünç sayfası bir kart taşır: başlık, kitabın durumu ve ödünç alma düğmesi. Düğmenin üç
ayrı tutamağı vardır — konumu, sınıf adı ve test kimliği (test id).

```js
// sayfa.mjs — odunc sayfasinin kucuk agac modeli
export const oge = (etiket, oznitelik = {}, cocuklar = [], metin = '') =>
  ({ etiket, oznitelik, cocuklar, metin });

export function sayfaKur(durum) {
  const dugme = oge('button', { sinif: 'birincil', kimlik: 'odunc-al' }, [],
    durum.dugmeMetni ?? 'Odunc Al');
  const kart = oge('div', { sinif: 'kart', kimlik: 'kitap-karti' }, [
    oge('h2', { sinif: 'baslik' }, [], 'Kaybolan Kitaplar'),
    oge('span', { sinif: 'durum', kimlik: 'kitap-durumu' }, [], durum.kitap),
    dugme,
  ]);
  return { kok: oge('main', {}, [oge('nav', {}, [], 'Katalog'), kart]), dugme, kart };
}
```

Dört seçici türü aynı düğmeyi dört ayrı yoldan arar: ağaçtaki konumdan, sınıf adından,
görünen metinden ve test kimliğinden. Gezilen düğüm sayısı sayılır; bu, seçicinin koşum
bedelidir.

```js
// secici.mjs — dort secici stratejisi ve gezilen dugum sayaci
export const sayac = { dugum: 0 };

function* gez(dugum) {
  sayac.dugum += 1;
  yield dugum;
  for (const cocuk of dugum.cocuklar) yield* gez(cocuk);
}

const ilk = (kok, olcut) => {
  for (const dugum of gez(kok)) if (olcut(dugum)) return dugum;
  return null;
};

const yol = (kok, indisler) => {
  let dugum = kok;
  for (const i of indisler) {
    sayac.dugum += 1;
    dugum = dugum.cocuklar[i];
    if (dugum === undefined) return null;
  }
  return dugum;
};

export const seciciler = {
  konum: (kok) => yol(kok, [1, 2]),
  sinif: (kok) => ilk(kok, (d) => d.oznitelik.sinif === 'birincil'),
  metin: (kok) => ilk(kok, (d) => d.metin === 'Odunc Al'),
  kimlik: (kok) => ilk(kok, (d) => d.oznitelik.kimlik === 'odunc-al'),
};
```

## Yapı Değişikliği Karşısında Dayanıklılık

Bir arayüz yaşadıkça değişir ve değişikliklerin çoğu davranışı değiştirmez: kart bir düzen
kapsayıcısına sarılır, sınıf adı yenilenir, düğmenin etiketi düzeltilir, kart içindeki
sıra değişir, ölçüm için bir öznitelik eklenir. Beş değişiklik ayrı ayrı, her seferinde
sıfırdan kurulan sayfaya uygulanır ve her seçicinin bulduğu öge asıl düğmeyle
karşılaştırılır.

```js
// dayaniklilik.mjs — bes yapi degisikligi karsisinda secici turleri
import { oge, sayfaKur } from './sayfa.mjs';
import { seciciler, sayac } from './secici.mjs';

const degisiklikler = {
  sarmalama: (s) => { s.kok.cocuklar[1] = oge('div', { sinif: 'duzen' }, [s.kart]); },
  'sinif adi': (s) => { s.dugme.oznitelik.sinif = 'eylem-birincil'; },
  'etiket metni': (s) => { s.dugme.metin = 'Odunc Alin'; },
  'kart sirasi': (s) => { s.kart.cocuklar = [s.kart.cocuklar[0], s.dugme, s.kart.cocuklar[1]]; },
  'izleme ozniteligi': (s) => { s.dugme.oznitelik.izleme = 'kart-birincil'; },
};

const s = (n, g) => String(n).padStart(g);
console.log(`${'secici'.padEnd(8)}${s('tamam', 7)}${s('bulunamadi', 12)}${s('yanlis oge', 12)}${s('dugum', 8)}`);

for (const [ad, secici] of Object.entries(seciciler)) {
  const sonuc = { tamam: 0, bulunamadi: 0, yanlis: 0 };
  sayac.dugum = 0;
  for (const uygula of Object.values(degisiklikler)) {
    const sayfa = sayfaKur({ kitap: 'rafta' });
    uygula(sayfa);
    const bulunan = secici(sayfa.kok);
    if (bulunan === sayfa.dugme) sonuc.tamam += 1;
    else if (bulunan === null) sonuc.bulunamadi += 1;
    else sonuc.yanlis += 1;
  }
  console.log(`${ad.padEnd(8)}${s(sonuc.tamam, 7)}${s(sonuc.bulunamadi, 12)}${s(sonuc.yanlis, 12)}${s(sayac.dugum, 8)}`);
}
```

```
secici    tamam  bulunamadi  yanlis oge   dugum
konum         3           1           1      10
sinif         4           1           0      30
metin         4           1           0      30
kimlik        5           0           0      30
```

Üç sonuç okunur. Birincisi sıralamadır: konuma bağlı seçici beş değişikliğin ikisinde,
sınıf ve metin seçicileri birinde, test kimliği hiçbirinde kırılmadı. Kimlik seçicisinin
dayanıklılığı bir üstünlük değil bir sözleşmedir — o öznitelik yalnız test için durur ve
tasarım değişiklikleri ona dokunmaz.

İkincisi, kırılmanın iki ayrı türüdür. `bulunamadi`, testin kırmızıya dönmesi ve nedenin
hemen okunmasıdır. `yanlis oge` ise sessizdir: kart içindeki sıra değiştiğinde konuma bağlı
seçici düğme yerine durum ögesini bulur, test ona dokunur ve hiçbir şey olmaz. Bu, kırılgan
testlerin en pahalı biçimidir; testin yeşil kalması da mümkündür, kırmızıya dönüp yanlış
yeri göstermesi de.

Üçüncüsü bedeldir. Konuma bağlı seçici beş aramada yalnız on düğüme dokundu, diğer üçü
otuz düğüme. Ağaç büyüdükçe bu fark büyür — ama otuz düğümlük gezinme, sessizce yanlış
ögeye dokunan bir testin bedeliyle karşılaştırılamaz.

## Bekleme Politikası

Sayfa hazır olmadan sürülemez, ama "hazır" tek bir an değildir: öge önce ağaca girer,
sonra tıklanabilir hâle gelir. Üç politika bu iki anla üç ayrı ilişki kurar. **Sabit uyku**
(sleep) belirli bir süre bekler ve bir kez dener. **Örtük bekleme** (implicit wait) ögenin
varlığını yoklar, göründüğü anda döner. **Açık bekleme** (explicit wait) beklenen koşulun
kendisini — burada tıklanabilirliği — yoklar.

Aşağıdaki koşum iki bin sayfa yüklemesini kendi tohumlu üretecinden üretir; sayfaların
yüzde ikisi ağır yüklenir, yüzde kırkında ögenin görünmesiyle tıklanabilir olması arasında
bir boşluk vardır.

```js
// bekleme.mjs — uc bekleme politikasinin dusme orani, tohum 20
function uretec(tohum) {
  let durum = (tohum * 2654435761) % 2147483647;
  return () => {
    durum = (durum * 48271) % 2147483647;
    return durum / 2147483647;
  };
}

const ARALIK = 25;
const ZAMAN_ASIMI = 1000;
const SABIT = 250;

const olayUret = (rastgele) => {
  const gorunme = rastgele() < 0.02 ? 900 + rastgele() * 600 : 20 + rastgele() * 180;
  const bosluk = rastgele() < 0.6 ? 0 : 10 + rastgele() * 200;
  return { gorunme, tiklanabilir: gorunme + bosluk };
};

const yokla = (olcut) => {
  let t = 0;
  let yoklama = 0;
  while (t <= ZAMAN_ASIMI) {
    yoklama += 1;
    if (olcut(t)) return { t, yoklama, ulasti: true };
    t += ARALIK;
  }
  return { t, yoklama, ulasti: false };
};

const politikalar = {
  'sabit uyku': (olay) => ({ basari: olay.tiklanabilir <= SABIT, bekleme: SABIT, yoklama: 1 }),
  'ortuk bekleme': (olay) => {
    const s = yokla((t) => olay.gorunme <= t);
    return { basari: s.ulasti && olay.tiklanabilir <= s.t, bekleme: s.t, yoklama: s.yoklama };
  },
  'acik bekleme': (olay) => {
    const s = yokla((t) => olay.tiklanabilir <= t);
    return { basari: s.ulasti, bekleme: s.t, yoklama: s.yoklama };
  },
};

const KOSUM = 2000;
const s = (n, g) => String(n).padStart(g);
console.log(`${'politika'.padEnd(15)}${s('dusme orani', 12)}${s('ort. bekleme', 14)}${s('yoklama', 9)}`);

for (const [ad, politika] of Object.entries(politikalar)) {
  const rastgele = uretec(20);
  let dusen = 0;
  let bekleme = 0;
  let yoklama = 0;
  for (let i = 0; i < KOSUM; i += 1) {
    const sonuc = politika(olayUret(rastgele));
    if (sonuc.basari === false) dusen += 1;
    bekleme += sonuc.bekleme;
    yoklama += sonuc.yoklama;
  }
  console.log(`${ad.padEnd(15)}${s(`%${((dusen / KOSUM) * 100).toFixed(1)}`, 12)}`
    + `${s(`${(bekleme / KOSUM).toFixed(0)} ms`, 14)}${s(yoklama, 9)}`);
}
```

```
politika        dusme orani  ort. bekleme  yoklama
sabit uyku            %16.0        250 ms     2000
ortuk bekleme         %40.9        136 ms    12878
acik bekleme           %1.4        181 ms    16477
```

Örtük beklemenin en düşük ortalama beklemeyi vermesi ve aynı zamanda en yüksek düşme
oranını üretmesi rastlantı değildir: yokladığı koşul yanlış koşuldur. Öge ağaca girdiği
anda döner, oysa test ögenin tıklanabilir olmasını istiyordu; aradaki boşluk her açıldığında
bir düşüş üretir. Sabit uyku ters uçtadır — her koşumda iki yüz elli milisaniye ödeyip
düşüşlerin altıda birini önler; hızlı yüklenen sayfada boşuna, ağır yüklenen sayfada
yetersizdir. Açık bekleme, hızlı sayfada erken döndüğü için ortalamada sabit uykudan ucuza
gelir ve düşme oranını yirmide birine indirir.

Açık beklemenin oranı sıfır değil, yüzde bir virgül dört. Kalan düşüşler zaman aşımını aşan
ağır yüklemelerdir. Bekleme politikası kırılganlığı yönetir, yok etmez; artan yoklama sayısı
da bunun bedelidir.

## Yakalanan ve Kaçan Kusur

Sayfayı süren bir test hangi kusur sınıfını yakalar? Ödünç alma isteği başarılı olduğu
hâlde kartın durum ögesinin güncellenmemesi bunlardan biridir — sunucu doğru yanıtı verir,
önceki dersin uçtan uca testi yeşil kalır, üye ekranda hâlâ "rafta" görür.

```js
// guncelle.mjs — surum 1: tiklamadan sonraki sayfa durumu
export const tiklaSonrasi = (durum) => ({ ...durum, odunc: true });
```

```js
// akis.test.mjs — odunc akisi sayfa modeli uzerinden surulur
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { sayfaKur } from './sayfa.mjs';
import { seciciler } from './secici.mjs';
import { tiklaSonrasi } from './guncelle.mjs';

const kimlikMetni = (kok, kimlik) => {
  if (kok.oznitelik.kimlik === kimlik) return kok.metin;
  for (const cocuk of kok.cocuklar) {
    const bulunan = kimlikMetni(cocuk, kimlik);
    if (bulunan !== null) return bulunan;
  }
  return null;
};

test('sinif adi degisse de kimlik secicisi dugmeyi bulur', () => {
  const sayfa = sayfaKur({ kitap: 'rafta' });
  sayfa.dugme.oznitelik.sinif = 'eylem-birincil';

  assert.equal(seciciler.kimlik(sayfa.kok), sayfa.dugme);
});

test('odunc alindiktan sonra kart durumu guncellenir', () => {
  const durum = { kitap: 'rafta' };
  const dugme = seciciler.kimlik(sayfaKur(durum).kok);
  assert.equal(dugme.etiket, 'button');

  const sonraki = sayfaKur(tiklaSonrasi(durum));

  assert.equal(kimlikMetni(sonraki.kok, 'kitap-durumu'), 'oduncte');
});

test('etiketi yanlis dugme de bulunur ve tiklanir', () => {
  const sayfa = sayfaKur({ kitap: 'rafta', dugmeMetni: 'Iade Et' });

  const dugme = seciciler.kimlik(sayfa.kok);

  assert.equal(dugme.etiket, 'button');
  assert.equal(dugme.oznitelik.kimlik, 'odunc-al');
});
```

```bash
node --test --test-reporter=tap akis.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
```

```
ok 1 - sinif adi degisse de kimlik secicisi dugmeyi bulur
not ok 2 - odunc alindiktan sonra kart durumu guncellenir
ok 3 - etiketi yanlis dugme de bulunur ve tiklanir
# tests 3
# pass 2
# fail 1
```

Düzeltme, tıklama sonrasındaki durumun kitabın durumunu da taşımasıdır.

```js
// guncelle.mjs — surum 2: kitap durumu da guncelleniyor
export const tiklaSonrasi = (durum) => ({ ...durum, odunc: true, kitap: 'oduncte' });
```

```bash
node --test --test-reporter=tap akis.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
```

```
ok 1 - sinif adi degisse de kimlik secicisi dugmeyi bulur
ok 2 - odunc alindiktan sonra kart durumu guncellenir
ok 3 - etiketi yanlis dugme de bulunur ve tiklanir
# tests 3
# pass 3
# fail 0
```

Üçüncü test kaçan sınıfı gösterir ve iki koşumda da yeşildir: düğmenin etiketi "Iade Et"
olsa bile test onu bulur, ona dokunur ve akış tamamlanır. Test kimliğine bağlanan seçici
etikete hiç bakmaz. Yani seçici dayanıklılığının bedeli, etiket metnindeki kusurun bu
testin kapsamından çıkmasıdır; o denetim ayrıca yazılmadıkça yanlış adlı bir düğme yeşil
koşumla yayına gider.

Bedel tarafında bu üç test bu koşumda 28,5 milisaniye sürdü; süre makineye bağlıdır ve
gerçek bir tarayıcı sürüldüğünde mertebe olarak büyür. Koşumdan bağımsız sayılar ise
ölçümlerde duruyor: sürülen ağaç altı düğümlüdür ve kimlik seçicisi beş aramada otuz düğüm
gezmiştir, açık beklemeli iki bin yüklemelik koşum ise on altı bin dört yüz yetmiş yedi
yoklama yapar.

## Özet

- Sayfayı süren testin iki kararı vardır: ögeyi hangi tutamaktan bulacağı ve hazır olmasını
  nasıl bekleyeceği.
- Beş yapı değişikliğinde konuma bağlı seçici iki kez, sınıf ve metin seçicileri birer kez
  kırıldı; test kimliği hiç kırılmadı.
- Kırılmanın iki türü ayrı bedellidir: bulunamama testi kırmızıya döndürür, yanlış öge
  eşleşmesi sessizce yanlış yere dokunur.
- İki bin yüklemede sabit uyku yüzde on altı, örtük bekleme yüzde kırk virgül dokuz, açık
  bekleme yüzde bir virgül dört düşme oranı verdi; örtük beklemenin ucuzluğu yanlış koşulu
  yoklamasından gelir.
- Sayfa testi kart güncellenmemesi kusurunu yakaladı, düğme etiketinin yanlışlığını
  kaçırdı; kimliğe bağlanan seçici etikete bakmaz.

## Sonraki Adım

Buradaki bütün ölçümler tek bir sayfa modeli üzerindeydi: tek ağaç, tek zaman çizelgesi.
Aynı arayüz bir avuç aygıtta, birden çok işletim sistemi sürümünde ve farklı ekran
genişliklerinde çalıştığında ise soru değişir. Artık hangi seçicinin dayanıklı olduğu
değil, testin **hangi aygıtlarda** koşacağı sorulur; çünkü aygıt ve sürüm çarpımı, koşum
sayısını sayfa sayısından çok daha hızlı büyütür. Sonraki ders bu çarpımın büyüme hızını,
seçilen alt kümenin kapsadığı kullanıcı payını ve kapsam dışında kalan kusur riskini ölçer.
