Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

JavaScriptでWebDriverのテストコードを書きましょ

7,627 views

Published on

ねこび〜ん by カネウチカズコ: http://ja.netbeans.org/nekobean
drawn by Cacoo: http://cacoo.com/

Published in: Technology
  • Be the first to comment

JavaScriptでWebDriverのテストコードを書きましょ

  1. 1. JAVASCRIPT ♥ WEB DRIVER @kuronekomichael 福岡Haxe勉強会 feat. HTML5+α @福岡 - 第0x00回
  2. 2. introduction 自動テストは誰もがやりたいと思うが、敷居が高い 特にUIテストは技術的にも運用的にも難しい 少しでも作業減らしたいよね Web Driverは意外と簡単に使えるよ JavaScriptで書けるのでディベロッパーも頑張れるよ メンテナブルなテストコードを書こうよ でもさ
  3. 3. Web Driverとは Googleが開発したWebアプリテストツール。 2011年にSeleniumと統合された。 Selenium2 === WebDriver RESTfulなHTTPプロトコル「Json Wire Protocol」で ブラウザの遠隔操作を実現 今日はRemote Web Driverの話だけやります JsonWireProtocol: https://code.google.com/p/selenium/wiki/JsonWireProtocol
  4. 4. WebアプリのUIテスト プラットフォーム毎/ブラウザ毎に、同じことを何度も何度も・・・ drawn by Cacoo: http://cacoo.com/
  5. 5. あ、IE9でも確認しておかないと… drawn by Cacoo: http://cacoo.com/
  6. 6. Androidが追加!2.xと4.xは別物なの?! drawn by Cacoo: http://cacoo.com/
  7. 7. GalaxyS4だけおかしい?AQUOS Phoneも? 何?部長がiOS7βにしただとおお?!? drawn by Cacoo: http://cacoo.com/
  8. 8. GAME OVER 部長マジくたばれ
  9. 9. DEMO
  10. 10. Web Driverの仕組み ・テストコードを元に操作を要求する「WebDriver クライアント」 ・HTTP経由で要求を受け取ってブラウザを操作する「WebDriver サーバ」  ブラウザを操作するための「ドライバ」(ブラウザ毎に用意されている) drawn by Cacoo: http://cacoo.com/ねこび∼ん by カネウチカズコ: http://ja.netbeans.org/nekobean
  11. 11. Web Driver Server 自前でWeb Driver Serverを準備したくないなら、Sause Labを使う手もあります https://saucelabs.com/php/se2/2 drawn by Cacoo: http://cacoo.com/ねこび∼ん by カネウチカズコ: http://ja.netbeans.org/nekobean
  12. 12. Web Driver Client Json Wire Protocolに従ったhttpリクエスト/レスポンスが 処理できれば、実装言語は何でもいい すでに言語毎に様々な実装有り(サードパーティ含む) ねこび∼ん by カネウチカズコ: http://ja.netbeans.org/nekobean
  13. 13. JavaScriptでのテストコード実装 Nodeで実行 ライブラリは選択肢多数 WebDriverJs(公式) jwebdriver webdriver.js burnout wd and etc. 今回は wd を使用
  14. 14. wdでの実装例 var wd = require('wd'), assert = require('assert'), browser = wd.remote({hostname: '10.0.2.19', port: 8080}); browser.init({browserName:'android'}, function(err, sessionId) { // ページを開く browser.get("http://demo.basercms.net/", function(err) { // 要素を取得 browser.elementByCssSelector('#global_menu .menu04 a', function(err, el) { // 要素の文字列をチェック el.text(function(err, text) { assert.equal(text, '新着情報'); // 終了 browser.quit(); }); }); }); }); wdはサンプルも豊富なので参考に。Json Wire ProtocolとAPIの対比表は、読み方に慣れが必要かも…。 wd document: https://github.com/admc/wd
  15. 15. より実践的に
  16. 16. 溢れだす欲求 ページを開く前にセッション情報(Cookie)を入れたい ページ毎にtitleが正しいかテストしたい 要素が存在するか判定したい アンカーをクリックしたい 要素をタップしたい エビデンス(スクリーンショット)を取りたい 非同期に表示される要素が出てから次に進みたい などなどなどなど
  17. 17. 実践例 1)事前にセッション情報(Cookie)を入れる 2)ページを開いて、意図したタイトルかテスト 3)必須要素が存在するかテスト 4)特定の要素をクリックして 意図したページへ遷移するかテスト 5)ページ毎にスクリーンショットを保存
  18. 18. DEMO
  19. 19. 1)事前にセッション情報を入れる // 古いセッション情報を削除 browser.deleteAllCookies(function(err) { // セッション情報を設定 browser.setCookie({name:'uuid', value:'...'}, function(err) { // 続きの処理 }); }); ※いったん全てのCookieを削除しているのは、Android DriverでBrowserのCookieを引き継いでしまうのを防ぐため
  20. 20. 残念! 他にもCookieを入れる必要がありました
  21. 21. 1)事前にセッション情報を入れる×3 // 古いセッション情報を削除 browser.deleteAllCookies(function(err) { // セッション情報を設定 browser.setCookie({name:'uuid', value:'...'}, function(err) { browser.setCookie({name:'cookie-P', value:'...'}, function(err) { browser.setCookie({name:'tutorial_flag', value:'true'}, function(err) { // 続きの処理 }); }); }); });
  22. 22. さあ!胡散臭くなってまりいました!
  23. 23. 2) ページを開いてタイトルをテスト // ページを開く browser.get("http://ncat.me/", function(err) { assert.ifError(err); // タイトルが意図した文字列かテスト browser.title(function(err, title) { assert.ifError(err); assert.ok(~title.indexOf('ネガネガ ネガにゃんこ')); // 続きの処理 }); });
  24. 24. 3) 必須要素が存在するかテスト // 画面が表示されるまで待つ browser.waitForVisibleByCssSelector('#mypageBtnPortal', 10 * 1000, function(err) { // 必須要素が存在するかテスト browser.elementByCssSelector('#mypageBtnPortal', function(err, element) { assert.ifError(err); // 続きの処理 }); });
  25. 25. 4) 要素をクリックして遷移をテスト // 特定の要素をクリック element.click(function(err) { assert.ifError(err); // 遷移先ページが表示されるまで待つ browser.waitForVisibleByCssSelector('#btnBack', 10 * 1000, function(err) { assert.ifError(err); // 続きの処理 }); });
  26. 26. 5) スクリーンショットを保存 // スクリーンショットを撮る browser.takeScreenshot(function(err, screenshot) { assert.ifError(err); fs.writeFile('screenshot.png', screenshot, 'base64', function(err) { assert.ifError(err); // もし続きがあればここに }); });
  27. 27. browser.init({browserName: 'android'}, function(err, sessionId) { assert.ifError(err); // ページを開く browser.get("http://ncat.me/dl/", function(err) { assert.ifError(err); // 古いセッション情報を削除 browser.deleteAllCookies(function(err) { assert.ifError(err); // セッション情報を設定 browser.setCookie({name:'uuid', value:'...'}, function(err) { assert.ifError(err); browser.setCookie({name:'cookie-P', value:'...'}, function(err) { assert.ifError(err); browser.setCookie({name:'tutorial_flag', value:'true'}, function(err) { assert.ifError(err); // Cookie付きで再びページを開く browser.get("http://ncat.me/", function(err) { assert.ifError(err); // タイトルが意図した文字列かテスト browser.title(function(err, title) { assert.ifError(err); assert.ok(~title.indexOf('ネガネガ ネガにゃんこ')); // 画面が表示されるまで待つ browser.waitForVisibleByCssSelector('#mypageBtnPortal', 10 * 1000, function(err) { // 必須要素が存在するかテスト browser.elementByCssSelector('#mypageBtnPortal', function(err, element) { assert.ifError(err); // 特定の要素をクリックして意図したページに遷移するかテスト element.click(function(err) { assert.ifError(err); browser.waitForVisibleByCssSelector('#btnBack', 10 * 1000, function(err) { assert.ifError(err); // スクリーンショットを撮る browser.takeScreenshot(function(err, screenshot) { assert.ifError(err); fs.writeFile('screenshot.png', screenshot, 'base64', function(err) { assert.ifError(err); browser.quit(); }); }); }); }); }); }); }); }); }); }); }); }); }); });
  28. 28. WELCOME TO CALLBACK HELL here come a Callback Monster Copyright © 2013 Warner Bros. Pictures / Picture from http://www.zekefilm.org/2013/07/11/tag-team-review-pacific-rim/
  29. 29. コールバック地獄 コールバックの連鎖に陥る危険については、 公式のドキュメントでも言及されている https://code.google.com/p/selenium/wiki/WebDriverJs#Understanding_the_API 対策 Control Flow? Promise? ・そもそも関数がまたがるのは直感的じゃない ・時系列に書きたい・読みたい
  30. 30. REDEMPTION FROM CALLBACK HELL 人類には yieldがある・・・! Copyright © 2013 Warner Bros. Pictures / Picture from http://www.prairiedogmag.com/review-pacific-rim-delivers-quality-entertainment-but-little-else/ We don't give up
  31. 31. REDEMPTION FROM CALLBACK HELL 人類には yieldがある・・・! Copyright © 2013 Warner Bros. Pictures / Picture from http://www.prairiedogmag.com/review-pacific-rim-delivers-quality-entertainment-but-little-else/
  32. 32. yield/generator 関数の実行を途中で中断して、 必要に応じて再開する機能 なんだ、夢でも見ているのか・・? ECMA Script6で導入が決定している 1. 2006/10 FireFox2で独自実装 (ECMA Script3拡張、JavaScipt1.7) 2. ECMAScript6(harmony)に導入決定 次世代JavaScriptに入ることが確定 3. 先行してV8に実装完了 4. Chrome Canary(Chrome開発版)には既に導入済み Node 0.12以降に導入済み ECMAScript 6draft: http://wiki.ecmascript.org/doku.php?id=harmony:generators
  33. 33. yieldの簡単な使用例 function* asyncCode() { console.log('初めの処理'); yield 1; console.log('何か終わった後の処理'); return 2; } // generatorの生成(まだ関数は実行されない) var gen = asyncCode(); // 1回めの実行 var ret = gen.next(); // コンソールには’初めの処理’が出力される // ret === {value:1, done:false} // 2回めの実行 ret = gen.next(); // コンソールには’何か終わった後の処理’が出力される // ret === {value:2, done:true}
  34. 34. wd-sync wdを拡張したモジュール yieldを使ってAPIを全て同期に置換えている 実はECMAScriptのyieldは使ってない(*ノω・*)テヘ wd-syncは fibers を使って同期を実現している fibersはJavaScriptだけではなくCのコードで同じ機能を実現させている 他にも無理矢理実現させているモジュールもあるみたい (関数を文字列化してsetTimeoutで無理矢理分割とか...)
  35. 35. wd-syncを使った実装 sync(function() { browser.init({browserName: 'android'}); // Cookieを設定するためにいったんサイトを開く browser.get("http://ncat.me/dl/"); // セッション情報を再設定 browser.deleteAllCookies(); browser.setCookie({name:'uuid', value:'...'}); browser.setCookie({name:'cookie-P', value:'...'}); browser.setCookie({name:'tutorial_flag', value:'true'}); // Cookieを設定したので、改めて開く browser.get("http://ncat.me/"); // タイトルが意図した文字列かテスト var title = browser.title(); assert.ok(~title.indexOf('ネガネガ ネガにゃんこ')); // 画面が表示されるまで待つ browser.waitForVisibleByCssSelector('#mypageBtnPortal', 10 * 1000); // 必須要素が存在するかテスト var element = browser.elementByCssSelector('#mypageBtnPortal'); assert.ok(element); // 特定の要素をクリックして意図したページに遷移するかテスト element.click(); browser.waitForVisibleByCssSelector('#btnBack', 10 * 1000); // スクリーンショットを取得 var screenshot = browser.takeScreenshot(); assert.ok(screenshot); fs.writeFileSync('screenshot.png', screenshot, 'base64'); });
  36. 36. ending 意外とWeb Driverは簡単 うまく動作しない時は、http req/resの中身を見る Json Wire Protocolは理解しやすいので一読オススメ コールバック地獄から抜けだそう ブラウザのコードではまだ地獄が続くけど… テストコードはシンプルが第一 やっぱりUIのテストは難しい 完璧は求めずに、やれることからやろう(not TDD) スモークテストでいいじゃない
  37. 37. 御清聴あざした!

×