Archive for 2014

CasperJS: Front End Testing Lebih Menyenangkan


Saya banyak bermain di back-end, tapi kadang harus melakukan testing front-end juga. Testing itu dilakukan untuk memastikan komunikasi antara back-end dan front-end berjalan dengan baik. Masalah muncul saat saya melakuan testing pada single web application yang pakai AJAX dan manipulasi DOM dimana-mana. Saya harus mengulangi langkah dari awal apabila error terjadi ditengah jalan.

Sebagai tukang ketik yang kadang malas saya pikir mengapa tidak dilakukan otomatis testingnya, ya macem pakai capybara di rails gitu. Tenyata hal itu dapat dilakukan dengan menggunakan headless browser. Headless browser adalah web browser yang tidak memiliki GUI. Nah karena tidak memiliki GUI, headless browser ini memiliki API yang dapat kita kendalikan dengan program tertentu. Dan enaknya lagi kita dapat dengan mudah menentukan lebar layar dari browser, ini penting saat membuat tampilan yang responsive.

Contoh dari headless browser diantaranya adalah Selenium dan PhantomJS. Pilihan saya jatuh pada PhantomJS, alasannya simple dependensinya sedikit dan lebih ringan. Untuk memudahkan testing dengan PhantomJS saya pakai CasperJS. CasperJS merupakan tool command line yang berjalan diatas PhantomJS dan difokuskan untuk navigation scripting dan testing tool. CasperJS menyediakan module tester untuk digunakan sebagai tool testing yang mirip seperti PHPUnit atau JUnit yang pake asert-asert itu lho.

Instalasi

Saya lebih suka pakai paket manager untuk instalasi tool-tool ini, kalau mau install manual juga boleh kok, panduanya ada di github repo Phantomjs dan CasperJS . Saya pakai npm untuk instalasinya
$ npm install pathomjs
$ npm install casperjs
Setelah instalasi selesai casperjs siap digunakan. Oh ya, kalau install-nya tidak global seperti contoh diatas, pastikan ./node_modules/.bin dimasukkan di PATH ya. Caranya bisa pakai ini:
$ export PATH=$PATH:$(pwd)/node_modules/.bin
Test hasil instalasinya:
$ casperjs 
CasperJS version 1.1.0-beta3 at ~/casperJS/node_modules/casperjs, 
using phantomjs version 1.9.7

Simple testing

Contoh untuk testing sederhana, buat file bernama simpleTest.js dan isi dengan kode berikut:
/* simpleTest.js */
var jumlahTest = 1;
/* ini test suit nya */
casper.test.begin('Mencoba testing', jumlahTest, function suite(test){
  // dimatiin dulu biar fokus
  casper.options.verbose = false; 
  casper.options.logLevel = 'debug';
  // kalau mau ganti lebar layar disini
  casper.options.viewportSize = {width: 1280, height: 800}; 

  // buka halaman web
  casper.start('http://a.anton.web.id/');

  casper.then(function(){
    /* kalau mau capture halaman pakai ini */
    //var imagePath = './homepage.png';
    //this.capture(imagePath);

    /* memastikan text di tag title benar */
    test.assertTitle('anton we', 'tag title = anton we');
  });
  
  // testing selesai
  casper.run(function(){
    test.done();
  });
});
Jalankan test:
$ casperjs test simpleTest.js

AJAX testing

Bagaimana dengan tombol atau link yang memanggil AJAX ketika diklik? Karena CasperJS ini berjalan di atas PanthomJS, kita dapat menggunakan .evaluate() untuk mengeksekusi javascript di dalam browser (dalam hal ini adalah PanthomJS). Lebih jelasnya bisa lihat gambar disini. Contoh untuk testing AJAX bisa dilihat dibawah ini, asumsinya kode ini ada didalam test suite nya CasperJS ya.
casper.then(function(){
  this.evaluate(function(){
    /* ini akan dieksekusi di browser */
    document.getElementById('tombolAjax').onclick();
  });
});

/* tunggu 5 detik, kadang ajax agak lama */
casper.wait(5000, function(){
  // melakukan sesuatu setelah 5 detik disini
});

/* atau bisa juga menunggu sampai resource siap */
casper.waitForResource('http://localhos/test-ajax', function(){
  // melakukan sesuatu saat resource test-ajax siap
});

Autocomplete testing

Testing autocomplete hampir sama dengan testing AJAX hanya sedikit lebih tricky. Bisa dilihat langsung dikode berikut:
casper.then(function(){
  var fieldAutocomplete = '#negara',
      isiDengan = 'ind';
      autocompleteSuggestContainer = '.ui-autocomplete';
  this.sendKeys(fieldAutocomplete, isiDengan, {keepFocus: true});
  this.waitUntilVisible(autocompleteSuggestContainer, function(){
    /* periksa autocomplete berjalan degan baik, ini opsional */
    this.capture('./cekAutocomplete.png');
    /* lakukan sesuatu setelah autocomplete berjalan,
       contoh klik link pertama: */
    this.click(autocompleteSuggestContainer + ' a:nth-of-type(0)'); 
  });
});
Lebih jauh tetang testing dengan CasperJS dapat dilihat pada dokumentasi tester module.Masih banyak lagi yang bisa dilakukan dengan CasperJS seperti scraping, mengisi form, mengirim form (post), mengirim form dengan AJAX dan lain-lain. Lebih detailnya bisa membaca dokumentasi dari CasperJS, tinggal disesuaikan dengan kebutuhan.

*Gambar dari https://plus.google.com/+CasperjsOrg

Macports: Error: org.macports.checksum

Today I added suhosin package with macports. But I found Error: org.macports.checksum for port ncurses. The error message indicates that downloaded package was broke. I tried to install it again and the error persists.

Fortunately, macports can get rid these kind of errors by cleaning up the damaged package using sudo port clean package_name command. You can install the package again after cleaning up.

Therefore, I use these commands to do that
sudo port clean ncurses
sudo port selfupdate // make sure the port is in the latest version
sudo port install ncurses
You can use it to do the same on other package, just change ncurses with the broken package name.

MySQL: Transaction Rollback Dan Kolom Auto Increment

Saya sudah sering memakai fitur transaction di MySQL, tapi baru beberapa hari yang lalu notice kalau ada yang nilai yang tidak kembali saat rollback. Nilai itu adalah nilai auto_increment pada kolom auto_increment. Bisa dilihat dibawah ini:
mysql> create table testauto(urutan int auto_increment primary key)engine=InnoDb;
Query OK, 0 rows affected (1.84 sec)

mysql> show create table testauto\G
*************************** 1. row ***************************
       Table: testauto
Create Table: CREATE TABLE `testauto` (
  `urutan` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`urutan`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into testauto(urutan) values(null);
Query OK, 1 row affected (0.00 sec)

mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                1 |
+------------------+
1 row in set (0.02 sec)

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from testauto;
Empty set (0.00 sec)

mysql> insert into testauto(urutan) values(null);
Query OK, 1 row affected (0.00 sec)

mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                2 |
+------------------+
1 row in set (0.00 sec)
Setelah membaca beberapa referensi, ternyata hal ini sudah dilaporkan sebagai bug sejak November 2004 (Bug #6714). Namun status dari bug tersebut adalah Won't fix, dikarenakan apabila nilai auto_increment di rollback akan muncul gap pada urutan yang dibuat seperti yang dituliskan pada komentar bug ini.
Solusinya? Pertama, bisa buat custom auto_increment dengan stored procedure atau trigger. Kedua, biarkan saja, toh auto_increment itu nilai maksimalnya besar dan jika masih kurang bisa pakai big int sebagai tipe data kolom tersebut.

PHP: Adding Custom Closure Parameter

PHP supported closure(anonymous function) since PHP 5.3. Sometimes extra parameter is needed to pass to those closure. PHP provide use keyword to do this job. Here is an example with array_filter():
$array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
$param = 5; // custom parameter
 
$r = array_filter($array, function($i) use ($param) { return $i < $param; });
// checking result
print_r($r);

Flask: WTForm How To Create Dynamic Fields

I had a problem with Flask-WTForm when I need to create dynamic fields. Trying to search how to solve this problem by googling for several hours. Finally I got the idea and solved this problem. This is how I can create dynamic fields with WTForm in flask.
Form:
class Costumer(Form):
  name = FieldList(TextField('Costumer Name'), validators = [Required()])
View:
@app.route('/newcostumer/', methods = ['POST'])
def newcostumer(total): // total is user generated value
  # set field attributes as nessesary like choices etc
  for i in range(total):
    if not form.is_submitted():
        form.name.append_entry()
        
  #validate after set form attributes
  if form.validate_on_submit():
    #get post data
    names = {}
    for i in range(total):
      name[i] = form.name[i].data
      
  # populate obj  
  if form.is_submitted():
        form.populate_obj(request.form)
  # render to newcostumer.html template
  return render_template('newcostumer.html', form = form, total = total)
Template:
# render form on newcostumer.html with jinja2
{% for i in range(total) %}
  {{form.name[i](id='cost'~i, class='costumer-class')}}
{% endfor %}
 
{% for error in form.errors %}
  [{{error}}]
{% endfor %}
This entry was posted in