PhntomJSでTest

PhantomJSって何?

PhantomJS is a headless WebKit scriptable with a JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.

PhantomJSって何?

  • ブラウザ無しにWebページのエミュレーションができる
  • さらにAPI経由で、Webページ上でJavaScriptが実行可能
  • つまり、Webの操作が何でもできる!

PhantomJSって何?

  • PhantomJSは、QtWebKitをベースに作られています。
  • DOMやCSSの操作だけでなく、CanvasやSVGまで扱えます。
  • 簡単にWebページのScreenShotが取れます。

PhantomJS基本編

まずは動かしてみる

PhantomJSのインストール

$ brew install phantomjs

PhantomJSを使ってみる1

  • sample1.js
  • $ phantomjs sample1.js
     スクリーンショットが`github.png`に保存される
var page = require('webpage').create();
page.open('https://github.com/', function() {
  page.render('github.png');
  phantom.exit();
});

PhantomJSを使ってみる2

  • sample2.js
  • $ phantomjs sample2.js
     githubのステータスがコンソールに出力される
var page = require('webpage').create();

page.open('https://status.github.com/', function(status) {
  if (status !== 'success') {
    console.error('fail to open page');
  } else {
    var msg = page.evaluate(function() {
      var msg = document.getElementById('message');
      return [msg.className, msg.innerText];
    });
    console.log('[' + msg[0] + '] ' + msg[1]);
  }
  phantom.exit();
});
[good] All systems operational

PhantomJS応用編

UnitTestで活用

FacebookのOAuth認証

  • FacebookのAccessTokenを取得してテストしたい
  • 普通はBrowserを開いてFacebookのページを表示する必要があるため、自動化が難しい
  • でも、事前に取得したAccessTokenを使う場合には期限切れの問題がある
  • それ、PhantomJSでできるよ!

OAuth認証の流れ

ここが問題

Authorization Code Grantの場合

Facebook認証の手順

  • → FacebookのOAuth認証DialogをRequest
  • ← Facebookのログイン画面が返ってくる
  • → 認証情報を入力してSubmit
  • ← OAuthのScopeをApproveする画面が返ってくる
  • → OKをClickする
  • ← RedirectURLに、Authorization Codeが渡される
  • → Authorization Codeを元にAccessToken取得
  • ← RedirectURLに、AccessTokenが渡される

OAuth認証の自動化のために

  • OAuth Serverに対してユーザ操作が必要
  • そこに PhantomJSを使ってあげる
    JavaScriptで出来る事は何でも可能!
    • 具体的には認証ダイアログに対して
      ID/Passwordを自動入力&Submit
    • さらにOAuthのApproveも自動でOKを押す

ようやく本題

phantomjs-nodeを使ってFacebookのAccessToken取得

TypeScript on Node

コードの簡単な解説

///<reference path="./typings/tsd.d.ts" />

import url = require('url');
import querystring = require('querystring');
import phantom = require('phantom');


var clientId = "1426039047681363";
var redirectUri = "http://localhost:3000/auth/facebook/callback";

コードの簡単な解説

function openOAuthDialog(ph: phantom.PhantomJS, callback: (err: any, token: string) => void): void {
  var params = {
    redirect_uri: redirectUri,
    response_type: 'code',
    client_id: clientId
  };
  openPage(ph, params, (page: phantom.WebPage, targetUrl: string): boolean => {
    var location = url.parse(targetUrl);
    var redirectLocation = url.parse(redirectUri);
    debuglog('location: ' + location.host + ', ' + location.pathname);

   (次のページで解説するので省略)

  }, callback);
}

コードの簡単な解説

  • ログイン画面とApprove画面の自動処理の実装部分
    ここが今回の一番のキモ。ただ中身は簡単。
    if (location.host === 'www.facebook.com') {
      switch (location.pathname) {
        case '/login.php':
          page.evaluate((login_email, login_password) => {
            var email = <HTMLInputElement>document.getElementById('email');
            if (email) {
              email.value = login_email;
              (<HTMLInputElement>document.getElementById('pass')).value = login_password;
              (<HTMLFormElement>document.getElementById('login_form')).submit();
            }
            }, () => {}, login_email, login_password);
          return false;
        case '/v2.0/dialog/oauth':
          page.evaluate(() => {
            (<HTMLFormElement>document.getElementById('platformDialogForm')).submit();
          });
          return false;
        default:
          return false;
      }
    } else if (location.host === redirectLocation.host) {
      var qs = querystring.parse(location.query);
      openAccessToken(ph, qs.code, callback);
      return true;
    }

コードの簡単な解説

  • RedirectURLに Authorization Codeが渡されるので、
    受け取っている部分。
  • その Codeを元にAccessToken取得のRequestを行う
    } else if (location.host === redirectLocation.host) {
      var qs = querystring.parse(location.query);
      openAccessToken(ph, qs.code, callback);
      return true;
    }

コードの簡単な解説

  • AccessTokenを受け取って callbackを呼び出す
function openAccessToken(ph: phantom.PhantomJS, code: string, callback: (err: any, token: string) => void): void {
  debuglog('code=' + code);
  var params = {
    client_id: clientId,
    redirect_uri: redirectUri,
    response_type: 'token',
    code: code
  };
  openPage(ph, params, (page: phantom.WebPage, targetUrl: string): boolean => {
    var location = url.parse(targetUrl);
    var redirectLocation = url.parse(redirectUri);
    debuglog('location: ' + location.host + ', ' + location.pathname);
    if (location.host === redirectLocation.host) {
      var qs = querystring.parse(location.hash);
      callback(null, qs['#access_token']);
      ph.exit();
      return true;
    } else {
      return false;
    }
  }, callback);
}

PhantomJSでテスト

By Hiroki Horiuchi