End-to-end Testing dengan The Intern

Apa itu end-to-end testing ?

Sebagai programer, tentu kita semua udah kenal yang namanya testing. Ada macam-macam test yang bisa kita pake untuk meningkatkan kualitas kode dan/atau aplikasi yang kita buat. Yang paling umum dan wajib adalah unit-test untuk memvalidasi alur/logika program dalam unit yang paling kecil yaitu function.

End-to-end testing, sering disingkat E2E, adalah untuk memvalidasi cara kerja aplikasi atau website dari sudut pandang seorang user. Jadi dalam E2E testing kita tulis skrip untuk mensimulasikan behavior atau tindakan-tindakan seorang user — mirip bikin bot.

Dalam artikel ini, saya akan bahas tentang E2E testing pake sebuah framework yang bernama The Intern. Ada beberapa framework yang bisa kita pake selain The Intern, antara lain: Cypress, Nightwatch, TestCafe, dan yang paling tua, Selenium. Berdasarkan pengalaman saya, The Intern adalah yang paling seimbang antara fitur & tingkat kerumitannya.

Instalasi

Sebelum mulai, pastikan dulu Anda punya NodeJS di komputer. Kalo belum silakan instal dulu.

Saya pake Mac/Linux. Jadi kalo Anda pake Windows, silakan sesuaikan sendiri perintahnya

Yuk kita mulai dengan pembuatan direktori kerja, sebut saja myproject. Dan karena The Intern adalah aplikasi NodeJS, langsung sekalian kita inisialisasi NPM & instal modulnya.

$ mkdir myproject && cd myproject
$ npm init -y
$ npm i -D intern 

Berikutnya, kita bikin direktori untuk skrip testing yang nanti kita buat & sekalian bikin file konfigurasi untuk The Intern.

$ mkdir testing
$ touch intern.json

Buka file intern.json & isi dengan konfigurasi seperti di bawah:

{
  "functionalSuites": ["tests/**/*.js"],
  "functionalTimeouts": {   
    "pageLoad": 10000,
    "connectTimeout": 10000, 
    "find": 5000
  },
  "environments": [
    {
      "browserName": "chrome",
      "fixSessionCapabilities": false
    }
  ]
}

functionalSuites menentukan di mana lokasi skrip testing yang harus dijalankan. Di bawahnya functionalTimeouts mengatur berapa lama waktu yang dipunyai The Intern sebelum men-trigger error. Jadi dalam setingan di atas, kalo page nggak muncul setelah 10 detik (10000ms), baru dianggap error. Kalo nyari elemen di halaman, 5 detik nggak ketemu baru error.

Setingan environments menentukan browser apa saja yang ingin kita pake untuk ngetes. Di sini kita pake Chrome aja.

Coba jalanin di terminal pake npx,

$ npx intern
TOTAL: tested 0 platforms, 0 passed, 0 failed

The Intern ini sebenernya juga bisa dipake untuk unit-test, tapi di sini kita fokus untuk functional-test aja. Lagian unit-test lebih enak kalo pake Jest.

Aplikasi Kalkulator

Kita coba ngetes aplikasi kalkulator yang ada di sini.

Bikin file tests/calculator.js, isinya begini:

const { suite, test, before } = intern.getPlugin('interface.tdd');
const { assert } = intern.getPlugin('chai');

suite('Kalkulator', ()=>{
  // function ini dijalanin satu kali sebelum test
  before(async ({remote})=>{
    // buka halaman yang mau dites
    await remote.get('https://projects.masputih.com/calculator/');
  })  

})

Test Suite adalah kumpulan beberapa tes yang konteksnya sama atau saling berhubungan. Di sini kita bikin Suite dengan nama Kalkulator. Function before, dijalanin satu kali sebelum The Intern menjalankan tes di dalam sebuah Suite.

Dalam kode di atas, sebelum jalanin tes kita buka dulu halaman yang mau dites pake remote.get(). Jadi remote itu apa? remote adalah objek yang dikirim The Intern ke semua tes, berisi API yang nantinya kita pake untuk mencari elemen di halaman yang kita tes dan lain-lain.

Dokumentasi tentang API apa aja yang bisa kita pake bisa dibaca di sini.

Sekarang coba jalanin The Intern. Chrome akan terbuka sebentar terus nutup sendiri.

$ npx intern
Listening on localhost:9000 (ws 9001)
Tunnel started

‣ Created remote session chrome 74.0.3729.157 on Mac OS X (cc252706f18da600d371b8e3bace5d88)
TOTAL: tested 1 platforms, 0 passed, 0 failed

Sekilas pas Chrome belum nutup, kita bisa liat status di bawah address bar seperti di bawah:

Sekarang coba kita cari id elemen yang jadi layar kalkulator. Pake Chrome, kita bisa liat id elemennya adalah answer.

Jadi kita update kode testingnya untuk ngetes apakah elemen layarnya ada dan tampilan awalnya adalah 0. Karena nanti kita bakal ngecek tampilan layar di dalam tes-tes yang lain, kita simpan referensi ke elemen #answer dalam variabel display.

const { suite, test, before } = intern.getPlugin('interface.tdd');
const { assert } = intern.getPlugin('chai');

suite('Kalkulator', ()=>{

  let display;

  before(async ({remote})=>{
    await remote.get('https://projects.masputih.com/calculator/');
  })  

  test('Kalkulator ada di page, tombol lengkap', async ({remote})=>{    

    display = await remote.findById('answer');
    const displayText = await display.getVisibleText();
    assert.equal(displayText,'0');

  })

})

Jalanin The Intern,

$ npx intern
Listening on localhost:9000 (ws 9001)
Tunnel started

‣ Created remote session chrome 74.0.3729.157 on Mac OS X (71f3f793bd62afa5ef01b199966a09bf)
✓ chrome 74.0.3729.157 on Mac OS X - Kalkulator - Kalkulator ada di page, tombol lengkap (0.042s)
TOTAL: tested 1 platforms, 1 passed, 0 failed

Semua API Intern bersifat asynchronous, jadi semua tes kita bisa ditulis pake async-await. Apa itu? Bisa dibaca sendiri di Javascript:Async-Await.

Berikutnya, kita pastiin semua tombol ada. Untuk tombol numerik kita simpen dalam array numericButtons. Tombol yang lain punya variabel sendiri-sendiri. Tujuannya biar kita bisa pake tombol-tombol itu dalam tes yang lain tanpa harus manggil API remote.findXXX lagi. Pertama, kita liat dulu atribut apa yang bisa kita pake untuk nyari button.

Dari Chrome dev tool kita liat, hampir semua button punya atribut value, sisanya ada yang pake id. Jadi di sini kita pake findDisplayedByCssSelector & findDisplayedById. Dan karena kita hanya mau ngetes tombol yang bersangkutan ada atau nggak, kita ngeceknya cukup pake assert.exists().

Dalam tutorial ini, saya pake API yang namanya assert yang lazim dipake dalam TDD (Test-Driven Development). Chai, library yang dipake oleh Intern juga mendukung jenis API yang lain yaitu expect dan should yang dipake dalam BDD (Behavior-Driven Development). Dokumentasi API-nya bisa dibaca sendiri di Chai:Assert dan Chai:BDD.

Mau pake API yang model TDD atau BDD itu hanya masalah selera aja. Oke?

Lanjut, kita update lagi tesnya untuk ngecek semua tombol.

const { suite, test, before } = intern.getPlugin('interface.tdd');
const { assert } = intern.getPlugin('chai');

suite('Kalkulator', ()=>{

  let display, 
    ACBtn, CEBtn, addBtn, subtractBtn, multiplyBtn, divideBtn, eqBtn, decimalBtn,
    numericButtons = [];

  before(async ({remote})=>{
    await remote.get('https://projects.masputih.com/calculator/');
  })

  test('Kalkulator ada di page, tombol lengkap', async ({remote})=>{    

    display = await remote.findById('answer');
    const displayText = await display.getVisibleText();
    assert.equal(displayText,'0');

    const btn0 = await remote.findDisplayedById('zeroButton');
    assert.exists(btn0);
    numericButtons.push(btn0);

    const btn1 = await remote.findDisplayedByCssSelector('button[value="1"]');
    assert.exists(btn1);
    numericButtons.push(btn1);
    
    const btn2 = await remote.findDisplayedByCssSelector('button[value="2"]');
    assert.exists(btn2);
    numericButtons.push(btn2);

    const btn3 = await remote.findDisplayedByCssSelector('button[value="3"]');
    assert.exists(btn3);
    numericButtons.push(btn3);

    const btn4 = await remote.findDisplayedByCssSelector('button[value="4"]');
    assert.exists(btn4);
    numericButtons.push(btn4);

    const btn5 = await remote.findDisplayedByCssSelector('button[value="5"]');
    assert.exists(btn5);
    numericButtons.push(btn5);

    const btn6 = await remote.findDisplayedByCssSelector('button[value="6"]');
    assert.exists(btn6);
    numericButtons.push(btn6);

    const btn7 = await remote.findDisplayedByCssSelector('button[value="7"]');
    assert.exists(btn7);
    numericButtons.push(btn7);

    const btn8 = await remote.findDisplayedByCssSelector('button[value="8"]');
    assert.exists(btn8);
    numericButtons.push(btn8);

    const btn9 = await remote.findDisplayedByCssSelector('button[value="9"]');
    assert.exists(btn9);
    numericButtons.push(btn9);

    decimalBtn = await remote.findDisplayedByCssSelector('button[value="."]');
    assert.exists(decimalBtn); 

    ACBtn = await remote.findDisplayedByCssSelector('button[value=ac]');
    assert.exists(ACBtn);    

    CEBtn = await remote.findDisplayedByCssSelector('button[value=ce]');
    assert.exists(CEBtn);    

    divideBtn = await remote.findDisplayedByCssSelector('button[value="/"]');
    assert.exists(divideBtn);    

    multiplyBtn = await remote.findDisplayedByCssSelector('button[value="*"]');
    assert.exists(multiplyBtn);

    subtractBtn = await remote.findDisplayedByCssSelector('button[value="-"]');
    assert.exists(subtractBtn);    

    addBtn = await remote.findDisplayedByCssSelector('button[value="+"]');
    assert.exists(addBtn);    

    eqBtn = await remote.findDisplayedById('equalButton');
    assert.exists(eqBtn);

  });


})

Jalanin Intern,

$ npx intern
Listening on localhost:9000 (ws 9001)
Tunnel started

‣ Created remote session chrome 74.0.3729.157 on Mac OS X (062d2f77bf3c360bccba6c786334a128)
✓ chrome 74.0.3729.157 on Mac OS X - Kalkulator - Kalkulator ada di page, tombol lengkap (0.572s)
TOTAL: tested 1 platforms, 1 passed, 0 failed

Berikutnya kita tes apakah tombol angka & layar display berfungsi.

const { suite, test, before } = intern.getPlugin('interface.tdd');
const { assert } = intern.getPlugin('chai');

suite('Kalkulator', ()=>{

  let display, 
    ACBtn, CEBtn, addBtn, subtractBtn, multiplyBtn, divideBtn, eqBtn, decimalBtn,
    numericButtons = [];

  before(async ({remote})=>{
    await remote.get('https://projects.masputih.com/calculator/');
  })

  test('Kalkulator ada di page, tombol lengkap', async ({remote})=>{    
     // ... dst
  });

  test('Tombol angka', async ({remote})=>{
    
    let displayText;

    // klik tombol angka 1
    await numericButtons[1].click();

    // cek display, harus berisi angka 1
    displayText = await display.getVisibleText();
    assert.equal(displayText, '1');

    // klik tombol angka 2
    await numericButtons[2].click();
    // cek display, harus berisi angka 12
    displayText = await display.getVisibleText();
    assert.equal(displayText, '12');

    // klik tombol angka 3
    await numericButtons[3].click();
    // cek display, harus berisi angka 123
    displayText = await display.getVisibleText();
    assert.equal(displayText, '123');
    
  })


})
$ npx intern
Listening on localhost:9000 (ws 9001)
Tunnel started

‣ Created remote session chrome 74.0.3729.157 on Mac OS X (ba742b32775ccbae55377bc8eb73ec6d)
✓ chrome 74.0.3729.157 on Mac OS X - Kalkulator - Kalkulator ada di page, tombol lengkap (0.555s)
✓ chrome 74.0.3729.157 on Mac OS X - Kalkulator - Tombol angka (0.147s)
TOTAL: tested 1 platforms, 2 passed, 0 failed

Lanjut, tes tombol AC & tombol-tombol operator,

const { suite, test, before } = intern.getPlugin('interface.tdd');
const { assert } = intern.getPlugin('chai');

suite('Kalkulator', ()=>{

  let display, 
    ACBtn, CEBtn, addBtn, subtractBtn, multiplyBtn, divideBtn, eqBtn, decimalBtn,
    numericButtons = [];

  before(async ({remote})=>{
    await remote.get('https://projects.masputih.com/calculator/');
  })

  test('Kalkulator ada di page, tombol lengkap', async ({remote})=>{    

    // ... dst

  });

  test('Tombol angka', async({remote})=>{
    
    // ... dst
    
  })

  test('Tombol AC', async ({remote})=>{

    await ACBtn.click();    
    
    let displayTextAfter = await display.getVisibleText();
    assert.equal(displayTextAfter,'0');

  })

  test('Tombol operator', async ({remote})=>{

    // + 
    await numericButtons[3].click();
    await addBtn.click();
    await numericButtons[1].click();
    await eqBtn.click();
    let displayText = await display.getVisibleText();
    assert.equal(displayText, '4');

    // -
    await subtractBtn.click();
    await numericButtons[1].click();
    await eqBtn.click();
    displayText = await display.getVisibleText();
    assert.equal(displayText, '3');

    // x
    await multiplyBtn.click();
    await numericButtons[3].click();
    await eqBtn.click();
    displayText = await display.getVisibleText();
    assert.equal(displayText, '9');

    // :
    await divideBtn.click();
    await numericButtons[2].click();
    await eqBtn.click();
    displayText = await display.getVisibleText();
    assert.equal(displayText, '4.5');

  })

})
$ npx intern
Listening on localhost:9000 (ws 9001)
Tunnel started

‣ Created remote session chrome 74.0.3729.157 on Mac OS X (e907e52d0d9f89e9e41315c5c0110782)
✓ chrome 74.0.3729.157 on Mac OS X - Kalkulator - Kalkulator ada di page, tombol lengkap (0.552s)
✓ chrome 74.0.3729.157 on Mac OS X - Kalkulator - Tombol angka (0.153s)
✓ chrome 74.0.3729.157 on Mac OS X - Kalkulator - Tombol AC (0.046s)
✓ chrome 74.0.3729.157 on Mac OS X - Kalkulator - Tombol operator (0.41s)
TOTAL: tested 1 platforms, 4 passed, 0 failed

Jadi begitulah caranya jalanin tes end-to-end atau functional test dengan The Intern. Banyak perintah/function yang bisa kita pake untuk query elemen DOM selain yang kita pake di atas. Daftarnya bisa dibaca sendiri di Leadfoot:Command.

Kalo mau nyoba nerusin testing sendiri bisa dicoba ngetes tombol EC & jalanin operasi matematika yang panjang.

Semoga bermanfaat.

Also in this category ...


Leave a Reply

Your email address will not be published. Required fields are marked *