İçeriğe geç
academia.sh

Ders 11 / 15

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.

İçindekiler

Ö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).

// 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.

// 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.

// 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.

// 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.

// guncelle.mjs — surum 1: tiklamadan sonraki sayfa durumu
export const tiklaSonrasi = (durum) => ({ ...durum, odunc: true });
// 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');
});
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.

// guncelle.mjs — surum 2: kitap durumu da guncelleniyor
export const tiklaSonrasi = (durum) => ({ ...durum, odunc: true, kitap: 'oduncte' });
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.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat