PhantomJSでHTML5の一部APIを使えるようにする方法

phantomjs

PhantomJS 1.9.xは古いWebKitを使っている

GUIを持たないヘッドレスブラウザのPhantomJSは、ウェブでのフロントエンド開発においてそのテストに適したツールです。
このPhantomJSは、QtWebKitをベースに開発されています。
HTML5やCSS3をふんだんに使ったイマドキのウェブサービスを開発している場合、PhantomJSを使ってテストしようとすると上手くいかない場合があります。
これは、使用しているWebKitが古いために起こる問題です。古いWebKitではJavaScriptCoreも古いので、JavaScriptの新しい機能も一部使えません。

どのような機能が使えないのかは、Supported Web Standards に明記されています。
このページには書かれていませんが、Web fontsも使えません。これは前回の記事で使えるようにする方法を書きました。

PhantomJS向けのshimsまとめ

前述の通り、PhantomJSは古いWebKitをベースにしているため、HTML5のAPIやJavaScriptの新機能が使えません。
自分が確認した限りで使えなかったAPIを使えるようにするshimをまとめました。
以下の機能が使えるようになります。

  • Function.prototype.bind
  • History API

今後も適宜更新していこうと思います。

https://gist.github.com/noradaiko/12cbaf8a1674e3b8c8e6.js

参考になれば幸いです!

関連資料

PhantomJS 1.9.xでウェブフォントを使う方法

phantomjs

ヘッドレスブラウザのPhantomJSはWeb fonts未対応

Web fontsは昨今とても一般的に使われるようになった技術です。
例えば、Twitter Bootstrapfont awesomeなどで、いわゆる “Glyphicons” として使われることが多いように思います。

PhantomJSというGUIを持たないブラウザがあります。
このブラウザは、ウェブサービスのフロントエンドのテスト自動化にとても便利なツールです。
JavaScriptで自動化のための処理を記述できて、スクリーンキャプチャも出来てとても便利です。

Web fontsに対応させることは出来るが再ビルドが必要

しかしながら、現行の1.9.xではWeb fontsに対応していません。
理由は、WOFFファイルにPhantomJSがまだ対応していないためです。
Web fontsを有効にするための手順 がありますが、PhantomJSをビルドしなおす必要があります。
ビルドは結構時間がかかり、大変です。

ビルド済みWeb Fonts対応版PhantomJS

PhantomJS with Webfonts Support – Binary Build – Arunoda Susiripala – Web Geek というサイトでは、ビルド済みのWeb Fonts対応版PhantomJSを公開しています。バージョンは1.9.0のようです。

ビルド済みのWeb Fonts対応版PhantomJS 1.9.0をダウンロード

作者に感謝しつつ、使いましょう!

PhantomJS + CasperJSで テスト終了時に警告が出まくる問題への対処方法

phantomjs

警告の内容

CasperJSでテストを書いていると、スクリプト終了時にPhantomJSが次のような大量の警告を吐いて止まらなくなります。

Unsafe JavaScript attempt to access frame with URL about:blank from frame with URL ...
Domains, protocols and ports must match.

これではまともにテスト結果が見られない上、CIも回せません。

PhantomJS v1.9.8はバギーなのでv1.9.7を使う

以下のスレッドにヒントが書かれていました。

To anyone who has a problem with this. Downgrade PhantomJS to 1.9.7 and run casperjs with the –ssl-protocol=tlsv1 to be functionally completely the same as PhantomJS 1.9.8.
There won’t be an updated PhantomJS 1.x version which fixes this issue.

Mac OSXでの v1.9.7 のインストール法

brewだと1.9.8が入ってしまいます。
バイナリを本家から直接ダウンロードして使います。

ただし、最新版へのリンクしかありませんので、バージョン番号を変えて直接リンクを開いてください。
一応、こちらにリンクも貼っておきます。

brewでインストールした既存のphantomjsはアンインストールしておくことを忘れないでください。

WebpackベースのアプリケーションをKarmaでテストする

banner

Webpackベースのアプリケーションをテストしたい

Webpackとは

Webpackはモジュールの依存関係を解決するプリプロセッサです。
依存関係の記述には、ES6、CommonJSやAMDのスタイルが使えます。
誤解を恐れずに言えば、node.js感覚で他のモジュールをrequireできるようにしてくれます。
webpackでソースコードをコンパイルすると、*.bundle.jsというような名前のファイルが出力されます。
これをアプリケーション側で事で、同期的・非同期的なモジュールの依存関係の解決が可能となります。

テストしやすい方法の検討

要件としては以下のとおりです:

  1. Webpackで書いたアプリケーションの各ユニットあるいは各ふるまいを、出来るだけ簡単にテストしたい
  2. 任意のCI(Continuous Integration)でも動かしたい

使用するツール群

前述の要件を満たすツールの組み合わせがたくさんあります。
今回は以下の構成でテストを走らせてみます:

  • Karma – テストランナー
  • PhantomJS – ヘッドレスブラウザ
  • Mocha – テストフレームワーク
  • Chai – アサーション
  • webpack – プリプロセッサ
  • bower – パッケージ管理

各ツールの詳しい説明は、それぞれの本家をご参照ください。

テストの実施準備手順

設定項目や手順の意味を理解するために、敢えてバラバラに分けて説明します。
筆者はMac OS Xの環境で行いました。

必要なパッケージのインストール

$ npm install --save-dev 
    karma 
    karma-chai 
    karma-mocha 
    karma-phantomjs-launcher 
    karma-webpack

Karmaの設定

karma.conf.jsで以下のファイルを作成します。

// Karma configuration
// Generated on Wed Nov 26 2014 23:12:23 GMT+0900 (JST)

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',


    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['mocha', 'chai'],


    // list of files / patterns to load in the browser
    files: [
      'test/**/*_test.js'
    ],


    // list of files to exclude
    exclude: [
    ],


    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
      // add webpack as preprocessor
      'test/**/*_test.js': ['webpack', 'sourcemap']
    },

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],


    // web server port
    port: 9876,


    // enable / disable colors in the output (reporters and logs)
    colors: true,


    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,


    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,


    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['PhantomJS'],


    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false,

    // karma watches the test entry points
    // (you don't need to specify the entry option)
    // webpack watches dependencies
    // webpack configuration
    webpack: {
    }

  });
};

Bowerのモジュールを読み込めるようにする

設定ファイル冒頭に以下を追記します:

var webpack = require("webpack");
var path = require("path");
var bowerPath = path.join( __dirname, "bower_components" );

webpackの設定に以下を追記します(参考: usage with bower)

    webpack: {
      resolve: {
        // Tell webpack to look in node_modules, then bower_components when resolving dependencies
        // If your bower component has a package.json file, this is all you need.
        modulesDirectories: ["node_modules", bowerPath]
      },
      // Define a new plugin that tells webpack to look at the main property in bower.json files when resolving dependencies.
      // For marionette, we need it to load the CJS version, which we specify with as ["main", "1"] in the args below.
      plugins: [
        new webpack.ResolverPlugin([
          new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin( "bower.json", ["main", ["main", "1"]] )
        ], ["normal", "loader"])
      ]
    }

これで、テストのソースコードからbowerのモジュールをrequire("jquery");というように記述して読み込めるようになりました。

テスト対象のサンプルモジュール

簡単なjqueryを用いるサンプルモジュールです。
index.js で以下のようなファイルを用意してください。

var $ = require('jquery');

module.exports = function() {
  var div = $('<div />');
  $('body').append(div);
  return div;
};

依存関係にある jquery をbowerでインストールしておきます:

$ bower install --save jquery

テストの用意

test/main_test.js で以下のようなファイルを作成します:

var createElement = require('../index.js');

describe('jQuery sample', function() {

  it('should create div element', function() {
    expect(createElement).to.be.Function;
    var div = createElement();
    expect(div).to.be.Array;
  });

});

処理内容としては、先ほど作成したサンプル関数(createElement)を読み込みます。
次にその関数を呼び出し、結果を検査しています。

テストの実行

以下のコマンドを実行します:

$ karma start

結果:

$ karma start
INFO [karma]: Karma v0.12.28 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
Hash: 606dfda4c9d20a78987a
Version: webpack 1.4.13
Time: 8ms
webpack: bundle is now VALID.
webpack: bundle is now INVALID.
Hash: 1b62ba3e4c6b38edee7e
Version: webpack 1.4.13
Time: 944ms
                Asset    Size  Chunks             Chunk Names
_js/test/main_test.js  693312       0  [emitted]  test/main_test.js
chunk    {0} _js/test/main_test.js (test/main_test.js) 247722 [rendered]
    [0] ./test/main_test.js 245 {0} [built]
    [1] ./index.js 126 {0} [built]
    [2] ./bower_components/jquery/dist/jquery.js 247351 {0} [built]
webpack: bundle is now VALID.
INFO [PhantomJS 1.9.8 (Mac OS X)]: Connected on socket Mfny5tbQCtBeW9kEu0TR with id 80467111
PhantomJS 1.9.8 (Mac OS X): Executed 1 of 1 SUCCESS (0.011 secs / 0.002 secs)

動きましたね!

さらに改善する

ソースマップ

Webpackでコンパイルしたテストがコケた時、示される行番号が参考にできません。
ソースマップを有効にすることで、コンパイル前の行番号を取得できます。

$ npm install --save-dev karma-sourcemap-loader

preprocessorsに加えます:

preprocessors: {
    'test/test_index.js': ['webpack', 'sourcemap']
}

また、webpackにソースマップを生成するよう設定します:

webpack: {
  // ...
    devtool: 'inline-source-map'
}

実行すると以下のように元の位置が併せて示されます:

PhantomJS 1.9.8 (Mac OS X) Hello test Is it right? FAILED
        TypeError: '[object Object]' is not a function (evaluating 'should(T)')
            at /Users/nora/Development/tmp/karma-test2/test/main_test.js:53:0 <- webpack:///./test/main_test.js:7:0
PhantomJS 1.9.8 (Mac OS X): Executed 1 of 1 (1 FAILED) ERROR (0.006 secs / 0 secs)