読者です 読者をやめる 読者になる 読者になる

にわとりプログラマーの備忘録

覚えたことをすぐ忘れてしまう、自分のための備忘録ブログです。

Scala.jsを触ってみた

Scala.jsというScalaで記述な可能なAltJSがあったので、興味本意で導入部分だけですが、触ってみました。

導入

早速、Scala.jsの導入を行っていきます。

プロジェクトの作成

activotr newコマンドでテンプレートからScalaのプロジェクトを作成します。

$ activator new

Fetching the latest list of templates...

Browse the list of templates: http://typesafe.com/activator/templates
Choose from these featured templates or enter a template name:
  1) minimal-akka-java-seed
  2) minimal-akka-scala-seed
  3) minimal-java
  4) minimal-scala
  5) play-java
  6) play-scala
(hit tab to see a list of all templates)
> 4

sbtの設定

project/plugins.sbtに以下を追記

addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.12")

build.sbtに以下を追記

enablePlugins(ScalaJSPlugin)

name := "Scala.js Tutorial"

scalaVersion := "2.11.7" // or any other Scala version >= 2.10.2

Hello Worldを表示するアプリケーションの作成

src/main/scala/com/example/Hello.scalaを作成

package com.example

import scala.scalajs.js.JSApp

object Hello extends JSApp {
  def main(): Unit = {
    println("Hello world")
  }
}

sbtを起動して上記のファイルを実行してみます。

$ sbt
> run
[info] Running com.example.Hello
Hello world
[success] Total time: 6 s, completed 2016/10/03 23:45:15

JavaScriptファイルに変換

fastOptJSコマンドでScalaのファイルをJavaScriptのファイルに変換します。

> fastOptJS
[success] Total time: 2 s, completed 2016/10/03 23:46:46

JavaScriptファイルを読み込む

実際にHTMLを作成して、先ほど生成したJavaScriptファイルを読み込んでみます。 index.htmlを以下の内容で作成します。

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Document</title>
   <script src="./target/scala-2.11/scalajs-fastopt.js"></script>
</head>
<body>
    <script type="text/javascript">
       com.example.TutorialApp().main()
   </script>
</body>
</html>

index.htmlでブラウザで読み込んでみます。

f:id:t-yng:20161003235930p:plain

無事に読み込まれ、コンソール上に"Hello World"と表示されています。

さいごに

正直、型安全をメリットとするならTypeScriptの方で良いなとは思いました。

phpblewでPHPのバージョン管理

PHPのバージョン管理ツールphpblewを導入したので、簡単に使い方をまとめておきます。

phpblewのインストール

$ curl -L -O https://github.com/phpbrew/phpbrew/raw/master/phpbrew
$ chmod +x phpbrew

# phpbrewを$PATHから参照できる位置に移動
$ mv phpbrew /usr/local/bin/phpbrew

phpbrewのセットアップ

以下のコマンドを実行してください。

$ phpbrew init

.bashrc or .zshrc に以下の記述を追記

[[ -e ~/.phpbrew/bashrc ]] && source ~/.phpbrew/bashrc

phpbrewを使ってみる

実際にphpbrewを使ってPHP7.0.4をインストールしていきます。

インストール可能なバージョン一覧を表示

$ phpbrew known

7.0: 7.0.11, 7.0.10, 7.0.9, 7.0.8, 7.0.7, 7.0.6, 7.0.5, 7.0.4 ...
5.6: 5.6.26, 5.6.25, 5.6.24, 5.6.23, 5.6.22, 5.6.21, 5.6.20, 5.6.19 ...
5.5: 5.5.38, 5.5.37, 5.5.36, 5.5.35, 5.5.34, 5.5.33, 5.5.32, 5.5.31 ...
5.4: 5.4.45, 5.4.44, 5.4.43, 5.4.42, 5.4.41, 5.4.40, 5.4.39, 5.4.38 ...

PHP7.0.4をインストール

$ phpbrew install 7.0.4 +default

Error: Configure failed:
The last 5 lines in the log file:
checking for alloca... (cached) yes

checking for working memcmp... yes

checking for stdarg.h... (cached) yes

checking for mcrypt support... yes

configure: error: mcrypt.h not found. Please reinstall libmcrypt.

エラーでコケました。

configure: error: mcrypt.h not found. Please reinstall libmcrypt.

libmcryptをインストールしてね。と言っているので、homebrewでインストールします。

$ brew install mcrypt

再び、インストールしてみます。

$ phpbrew install 7.0.4 +default

古いバージョンのPHPをインストール

https://github.com/phpbrew/phpbrew/wiki/TroubleShooting#version--5322-not-found

インストール中のバージョン一覧を表示

$ phpbrew list
* (system)
  php-7.0.4

バージョンの切り替え

$ phpbrew use 7.0.4

バージョンの確認

$ php --version
PHP 7.0.4 (cli) (built: Sep 30 2016 01:25:09) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies

サーバーサイドをTypeScriptで開発する

型があるJavaScriptを書きたいなと思い、サーバーサイドをTypeScriptで開発する方法を調べてみました。

開発環境

OS: Mac
TypeScript: 2.0.3

Node.jsをインストール

Node.jsのインストール方法は、Node.jsでHello Worldをご参考ください。

TypeScriptのインストール

TypeScriptのインストールは、ターミナル上で以下のコマンドを実行します。

$ npm i -g typescript
$ tsc --version
Version 2.0.3

型定義ファイルをのインストール

typescriptではJavaScriptで実装されたライブラリの型チェックを行うために、.d.ts拡張子の型定義ファイルを用意する必要があります。

以前まではtsdtypings等の管理ツールで管理をする必要があったのですが、TypeScript2.0よりnpmのみで管理することが出来るようになりました。

参考: 特別なツール不要! TypeScript 2.0時代の型定義ファイルの取り扱い方

npmを利用した型定義ファイルのインストールは以下の形式で行うことができます。
利用可能な型定義ファイルについてはTypes Search上で検索することが出来ます。

$ npm i --save @types/<型定義ファイル名>

package.jsonを作成して、node.jsの型定義ファイルをインストールします。

$ npm init
$ npm i --save @types/node

vscodeでintellijence機能を使うためにtsconfig.jsonも生成しておきます。

$ tsc --init

処理を実装

以下の内容をserver.tsというファイル名で保存します。
TypeScript2.0よりreferenceで型定義ファイルのパスの指定が不要になりました。

import http = require('http')

class Server {
    constructor() {
        const server: http.Server = http.createServer(
            (request: http.ServerRequest, response: http.ServerResponse) => {
                this.requestHandler(request, response)
            })
        
        server.listen('5000')
    }

    private requestHandler(request: http.ServerRequest, response: http.ServerResponse):void {
        response.end('Hello World')
    }
}

const server:Server = new Server()

TypeScriptのコンパイル

以下のコマンドで、server.tsコンパイルします。

$ tsc server.ts

ブラウザで確認する

無事にコンパイルが成功した場合は、同じ階層内にserver.jsが生成されています。 以下のコマンドで生成されたserver.jsを実行して、http://localhost:5000にアクセスすれば下の画像のように表示されると思います。

$ node server.js

f:id:t-yng:20160929005113p:plain

vscodeでTypeScript2.0を利用する設定

Code > Preferences > Workspace Settingsに以下の記述を追記

{
  // typescriptをグローバルインストールしている場合
  "typescript.tsdk": "/usr/local/lib/node_modules/typescript/lib"
  
  // typescriptをローカルインストールしている場合
  // "typescript.tsdk": "/usr/local/lib/node_modules/typescript/lib"
}

さいごに

前にも一度TypeScriptを少しだけ触ったことがあったのですが、TypeScript2.0になってから、型定義ファイルの扱いがかなり楽になっていました。
改めて少しTypeScript勉強しようかなと思います。

Dockerで簡単なウェブアプリケーションを実行してみた

Docker for Mac が正式にリリースされたので、簡単に触ってみようと思い、公式リファレンスにあるRun a simplle applicationを試してみました。

Doker for Macをインストール

https://docs.docker.com/docker-for-mac/からStable版のインストーラーをダウンロードして実行します。
表示される実行手順に沿って、dockerをインストールします。

インストールの確認

dockerをインストールしたら、dockerを起動します。
右上にdockerアイコン(クジラのマーク)が表示されていれば、起動は完了しています。
ターミナルを起動して、docker run hello-worldと入力してみます。
無事インストールされていれば、下記のように表示されます。

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
535020c3e8ad: Pull complete
af340544ed62: Pull complete
Digest: sha256:a68868bfe696c00866942e8f5ca39e3e31b79c1e50feaee4ce5e28df2f051d5c
Status: Downloaded newer image for hello-world:latest

Hello from Docker.
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker Engine CLI client contacted the Docker Engine daemon.
2. The Docker Engine daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker Engine daemon created a new container from that image which runs the
   executable that produces the output you are currently reading.
4. The Docker Engine daemon streamed that output to the Docker Engine CLI client, which sent it
   to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker Hub account:
https://hub.docker.com

For more examples and ideas, visit:
https://docs.docker.com/userguide/

docker ps -aと入力することで、システム上で動作中の全てのコンテナを確認できます。

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES
b1f484d71bee        hello-world         "/hello"                 16 seconds ago      Exited (0) 15 seconds ago                       high_pasteur
610780e466ba        hello-world         "/hello"                 9 minutes ago       Exited (0) 9 minutes ago                        elated_newton
33dbe3b0329c        nginx               "nginx -g 'daemon off"   37 minutes ago      Exited (0) 13 minutes ago                       nostalgic_bohr
5436f43d1c8a        hello-world         "/hello"                 38 minutes ago      Exited (0) 38 minutes ago                       suspicious_wright

簡単なアプリケーションを実行してみる

実際にRun a simplle applicationを試していきます。
以下の記述は、ほぼ上記ページの和訳になります。

練習用のwebアプリケーションを実行

docker runコマンドを利用して、PythonのFlask applicationを実行してみます。

$ docker run -d -P training/webapp python app.py

-dオプションは、Dockerコンテナをバックグラウンドで実行することを指定します。 -Pオプションは、ホストのネットワークのポートとコンテナ内のポートをマッピングするオプションです。

実行したDockerコンテナを確認する

docker psコマンドを利用することで、起動中のコンテナを確認することができます。

$ docker ps -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                     NAMES
5ad9fed174e9        training/webapp     "python app.py"     5 seconds ago       Up 5 seconds        0.0.0.0:32770->5000/tcp   silly_almeida

-lオプションを指定することで、コンテナの詳細情報を表示することができます。

ブラウザ上で確認してみる

PORTS
0.0.0.0:32770->5000/tcp

上記のネットワークポートのカラムを見てみると、ホストのポート32770番がコンテナ内の5000番ポートにマッピングされていることが、分かります。

htpp://localhost:32700にアクセスすることで、実行中のwebアプリケーションにアクセスすることができます。

f:id:t-yng:20160822232134p:plain

マッピングされているポートの確認

docker portコマンドを利用することで、コンテナ内の特定のポート番号にマッピングされているホストのポートを調べることができます。

$ docker port silly_almeida 5000
0.0.0.0:32770

ログを確認

docker logsコマンドで、コンテナのログを表示することができます。

$ docker logs -f silly_almeida
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
172.17.0.1 - - [22/Aug/2016 13:41:28] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [22/Aug/2016 13:41:28] "GET /favicon.ico HTTP/1.1" 404 -

-fオプションは、ログをリアルタイムで表示します。

コンテナ上で実行されているプロセスを確認

docker topコマンドを利用します。

$ docker top silly_almeida
PID                 USER                TIME                COMMAND
2918                root                0:00                python app.py

上記の実行結果では、コンテナ上でpyton app.pyプロセスのみが実行中であることが確認できます。

コンテナの詳細情報を表示

docker inspectを利用します。 上記コマンドはJSON形式でコンテナの詳細設定等の情報を返してくれます。

$ docker inspect silly_almeida
[
    {
        "Id": "5ad9fed174e9005f4da948bc3d2d6f60761a0dd5f0b2772ef3ccd6abq3224d633",
        "Created": "2016-08-22T13:38:46.052823893Z",
        "Path": "python",
        "Args": [
            "app.py"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 2918,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2016-08-22T13:38:46.621722368Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:6fae60ef344644649a39240b94d73b8ba9c67f898ede85cf8e947a887b3e6557",
        "ResolvConfPath": "/var/lib/docker/containers/5ad9fed174e9005f4da948bc3d2d6f60761a0dd5f0b2772ef3ccd6ab3224d633/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/5ad9fed174e9005f4da948bc3d2d6f60761a0dd5f0b2772ef3ccd6ab3224d633/hostname",
...

コンテナを停止

docker stopコマンドを利用することで、コンテナを停止させます。

$ docker stop silly_almeida
silly_almeida

docker ps -lでコンテナの状態を確認してみます。

$ docker ps -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                        PORTS               NAMES
5ad9fed174e9        training/webapp     "python app.py"     26 minutes ago      Exited (137) 20 seconds ago                       silly_almeida

停止しています。

コンテナを再起動させる

docker startでコンテナを再起動できます。

$ docker start silly_almeida
silly_almeida

コンテナを削除

最後に、今回のコンテナは特に必要ないので削除します。 コンテナの削除にはdocker rmを使います。

$ docker rm silly_almeida
Error response from daemon: You cannot remove a running container 5ad9fed174e9005f4da948bc3d2d6f60761a0dd5f0b2772ef3ccd6ab3224d633. Stop the container before attempting removal or use -f

削除に失敗しました。 Dockerでは起動中のコンテナは削除できないので、削除したい場合は一度コンテナを停止させる必要があります。

$ docker stop silly_almeida
silly_almeida

$ docker rm silly_almeida
silly_almeida

まとめ

dockerの簡単なコンテナの操作をやってみました。 次はもう少し複雑なことをやってみたいです。

babel+webpackでビルド可能なchrome拡張の雛形をYoemanで作成

generator-generatorのインストール

$ npm i -g generator-generator

雛形の雛形を生成

$ mkdir generator-chrome-extension
$ cd generator-chrome-extension
$ yo generator

yoemanからgeneratorとして呼び出させるようにするために、以下のコマンドをgenerator-chrome-extension内で実行します。

$ npm link

generator-chrome-extensionが必要になくなった場合は、以下のコマンドでnpm linkを取り消すことができます。

$ npm unlink -g generator-chrome-extension

雛形をカスタマイズ

最初にgenerators/app/templates以下に雛形として生成するファイルやディレクトリを配置します。

.
├── index.js
└── templates
    ├── _bower.json
    ├── _manifest.json
    ├── _package.json
    ├── dist
    ├── js
    │   ├── background.js
    │   └── main.js
    └── webpack.config.js

続いて雛形生成の処理を記述していきます。
yoemanが雛形を生成する処理はgenerators/app/index.jsに記述してあるため、こちらを編集することで雛形生成の処理をカスタマイズすることが出来ます。

'use strict';
var yeoman = require('yeoman-generator');
var chalk = require('chalk');
var yosay = require('yosay');
var mkdirp = require('mkdirp')

module.exports = yeoman.Base.extend({
  prompting: function () {
    // Have Yeoman greet the user.
    this.log(yosay(
      'Welcome to the fine ' + chalk.red('generator-chrome-extension') + ' generator!'
    ));

    // 設定ファイルに記述する値を対話的に入力してもらう処理
    var prompts = [
      {
        name: 'name',
        message: 'What is your app name',
        default: ''
      },
      {
        name: 'description',
        message: 'What is your app description',
        default: ''
      },
      {
        name: 'author',
        message: 'What is your name',
        default: ''
      }
    ];

    return this.prompt(prompts).then(function (props) {
      // To access props later use this.props.someAnswer;
      this.props = props;
    }.bind(this));
  },

  // 雛形のファイルやディレクトリを書き込み
  writing: function () {
    // ディレクトリをコピー
    this.directory(
      this.templatePath('js'),
      this.destinationPath('js')
    )

    // 空のディレクトリを生成
    mkdirp(this.destinationPath('dist'))

    this.fs.copyTpl(
      this.templatePath('_bower.json'),
      this.destinationPath('bower.json'),
      {
        name: this.props.name,
        description: this.props.description
      }
    )

    this.fs.copyTpl(
      this.templatePath('_manifest.json'),
      this.destinationPath('manifest.json'),
      {
        name: this.props.name,
        description: this.props.description
      }
    )

    this.fs.copyTpl(
      this.templatePath('_package.json'),
      this.destinationPath('package.json'),
      {
        name: this.props.name,
        description: this.props.description,
        author: this.props.author
      }
    )

    this.fs.copy(
      this.templatePath('.babelrc'),
      this.destinationPath('.babelrc')
    )

    this.fs.copy(
      this.templatePath('webpack.config.js'),
      this.destinationPath('webpack.config.js')
    )

  },

  // bower & npm のインストール実行
  install: function () {
    this.installDependencies();
  }
});

こちらの記述はプロンプトでユーザーにプロジェクト名等を入力してもらい、prompt変数に格納していきます。

  prompting: function () {
    // Have Yeoman greet the user.
    this.log(yosay(
      'Welcome to the fine ' + chalk.red('generator-chrome-extension') + ' generator!'
    ));

    // 設定ファイルに記述する値を対話的に入力してもらう処理
    var prompts = [
      {
        name: 'name',
        message: 'What is your app name',
        default: ''
      },
      {
        name: 'description',
        message: 'What is your app description',
        default: ''
      },
      {
        name: 'author',
        message: 'What is your name',
        default: ''
      }
    ];

    return this.prompt(prompts).then(function (props) {
      // To access props later use this.props.someAnswer;
      this.props = props;
    }.bind(this));
  },

templates/_package.jsonの中身を見てみます。
<%= %>で囲まれた部分が入力さた値が埋め込まれる箇所を示しています。

{
  "name": "<%= name %>",
  "version": "1.0.0",
  "description": "<%= description %>",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --watch"
  },
  "author": "<%= author %>",
  "license": "ISC",
  "devDependencies": {
    "babel-cli": "^6.11.4",
    "babel-core": "^6.11.4",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.9.0",
    "webpack": "^1.13.1"
  },
  "dependencies": {
    "lodash": "^4.13.1"
  }
}

この雛形ファイルに値を埋め込むにはthis.fs.copyTpl()を利用します。
上記のindex.jsでは次のように処理を書いています。
第1引数にテンプレートファイルのパスを、第2引数に出力先のファイルパスを、第3引数として設定ファイルに埋め込む変数の値をオブジェクトとして渡してあげます。

this.fs.copyTpl(
  this.templatePath('_package.json'),
  this.destinationPath('package.json'),
  {
    name: this.props.name,
    description: this.props.description,
    author: this.props.author
  }
)

generator-chrome-extensionの動作確認

ディレクトリに移動して、generator-chrome-extensionの動作確認をしてみます。

$ mkdir test-chrome-extension
$ cd test-chrome-extension
$ yo chrome-extension

プロンプトが立ち上がり必要な値を入力し終わると、ファイル・ディレクトリの生成が行われた後に、bower install && npm installが実行され雛形が生成されます。

$ tree
.
├── bower.json
├── bower_components (省略)
├── dist
├── js
│   ├── background.js
│   └── main.js
├── manifest.json
├── node_modules (省略)
├── package.json
└── webpack.config.js

参考

.gitignoreはgiboで自動生成しよう

最近、.gitignoreを書く時に毎回同じことを書いていて、面倒だなと思い、調べてみたところgiboと呼ばれる.gitignoreの自動生成ツールがありました。

giboのインストール

Homeblew経由でインストールできます。

$ brew install gibo

使い方

一覧を表示

giboJavaPHPOSX など様々に対応した.gitignoreを生成可能です。
以下のコマンドを入力することで、giboが対応している言語やツールなどの一覧を確認することができます。

$ gibo --list
=== Languages ===

Actionscript        GitBook         Python
Ada         Go          Qooxdoo
Agda            Gradle          Qt
Android         Grails          R
AppceleratorTitanium    GWT         Rails
AppEngine       Haskell         RhodesRhomobile
ArchLinuxPackages   Idris           ROS
Autotools       IGORPro         Ruby
C++         Java            Rust
C           Jboss           Sass
CakePHP         Jekyll          Scala
CFWheels        Joomla          Scheme
ChefCookbook        Julia           SCons
Clojure         KiCad           Scrivener
CMake           Kohana          Sdcc
CodeIgniter     LabVIEW         SeamGen
CommonLisp      Laravel         SketchUp
Composer        Leiningen       Smalltalk
Concrete5       LemonStand      Stella
Coq         Lilypond        SugarCRM
CraftCMS        Lithium         Swift
CUDA            Lua         Symfony
D           Magento         SymphonyCMS
Dart            Maven           Terraform
Delphi          Mercury         TeX
DM          MetaProgrammingSystem   Textpattern
Drupal          Nanoc           TurboGears2
Eagle           Nim         Typo3
Elisp           Node            Umbraco
Elixir          Objective-C     Unity
Elm         OCaml           UnrealEngine
EPiServer       Opa         VisualStudio
Erlang          OpenCart        VVVV
ExpressionEngine    OracleForms     Waf
ExtJs           Packer          WordPress
Fancy           Perl            Xojo
Finale          Phalcon         Yeoman
ForceDotCom     PlayFramework       Yii
Fortran         Plone           ZendFramework
FuelPHP         Prestashop      Zephir
Gcov            Processing

=== Global ===

Anjuta          JDeveloper      Redcar
Archives        JetBrains       Redis
Bazaar          Kate            SBT
BricxCC         KDevelop4       SlickEdit
Calabash        Lazarus         SublimeText
Cloud9          LibreOffice     SVN
CodeKit         Linux           SynopsysVCS
CVS         LyX         Tags
DartEditor      Matlab          TextMate
Dreamweaver     Mercurial       TortoiseGit
Dropbox         MicrosoftOffice     Vagrant
Eclipse         ModelSim        Vim
EiffelStudio        Momentics       VirtualEnv
Emacs           MonoDevelop     VisualStudioCode
Ensime          NetBeans        WebMethods
Espresso        Ninja           Windows
FlexBuilder     NotepadPP       Xcode
GPG         OSX         XilinxISE
IPythonNotebook     Otto

.gitignoreの生成

例えば、以下のコマンドでOSXでgit管理外にすべきファイルの一覧を表示してくれます。

$ gibo OSX
### https://raw.github.com/github/gitignore/9f6724149b9a0a861b402683f6c50c5f085d130b/Global/OSX.gitignore

*.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

この出力結果をそのまま.gitignoreに出力して上げることで、簡単に.gitignoreを生成することができます。

$ gibo OSX >> .gitignore

おわりに

giboを使うことで、いちいち.gitignoreに追加するためのファイルをネットで検索する手間が省けるので、毎回検索している人にはオススメのツールです。

Scala + Selenium を使ってブラウザの操作を自動化してみる

Seleniumとは?

Seleniumとはブラウザをプログラムから操作することでUIテストを自動化してくれる
テストツールです。

対応言語

Seleniumは多くの言語に対応しており Java, C#, python, ruby, php, perl, javascript(node.js) にて処理を記述できます。 Javaで記述できるということで、Scalaを使ってブラウザの操作を自動化してみたいと思います。

準備

プロジェクトの作成

activatorを利用してテンプレートからプロジェクトを作成します。

$ activator new selenium_test

Fetching the latest list of templates...

Browse the list of templates: http://typesafe.com/activator/templates
Choose from these featured templates or enter a template name:
  1) minimal-akka-java-seed
  2) minimal-akka-scala-seed
  3) minimal-java
  4) minimal-scala
  5) play-java
  6) play-scala
(hit tab to see a list of all templates)
> 4
OK, application "selenium_test" is being created using the "minimal-scala" template.

build.sbtの記述

build.sbtにselenium-javaを追記します。

libraryDependencies ++= Seq(
  "org.scalatest" %% "scalatest" % "2.2.4" % "test",
  "org.seleniumhq.selenium" % "selenium-java" % "2.53.0"
)

ChromeDriverをインストール

SeleniumはデフォルトではFirefoxを起動するようになっています。
Chromeを操作したい場合は、個別でChromeDriverをインストールする必要があります。
最初にChromeDriverのダウンロードページからzipファイルをダウンロードします。 その後、ファイルを解凍しシンボリックリンクを作成しておきます。

$ curl -O http://chromedriver.storage.googleapis.com/2.22/chromedriver_mac32.zip
$ unzip chromedriver_mac32.zip
$ rm -r chromedriver_mac32.zip
$ mkdir ~/.selenium
$ mv chromedriver ~/.selenium/
$ ln -s ~/.selenium/chromedriver /usr/local/bin/chromedriver

コードの記述

今回は下記の一連の流れを自動化するするための、コードを記述します。 1. Chromeを起動してgoogleにアクセス 2. 「selenium」について検索 3. 検索結果の画面を(無駄に)上下にスクロール 4. 検索結果のQuitaの記事を表示 5. 表示した記事の中の参考動画のリンクを表示

package com.example

import org.openqa.selenium.chrome.ChromeDriver

object SeleniumTest {

  def main(args: Array[String]): Unit = {

    val driver = new ChromeDriver()

    // Googleのページにアクセス
    driver.get("http://google.co.jp")

    // 検索フォームの要素を取得
    val element = driver.findElementByName("q")
    // 検索フォームに「selenium」と入力
    element.sendKeys("selenium")

    // 検索ボタンの要素を取得して、クリック
    val btn = driver.findElementByName("btnG")
    btn.click()

    // DOMが構築されるまで少し待機
    Thread.sleep(2000)

    // JavaScriptを実行して、画面を上下にスクロール
    {List.fill(5)(250):::List.fill(5)(-250)}.foreach(y => {
      driver.executeScript(s"window.scrollBy(0,${y});")
      Thread.sleep(500)
    })

    val clickLink = (linkText: String) => {
      val link = driver.findElementByLinkText(linkText)
      link.click()
    }

    // 検索結果のリンクをクリック
    clickLink("WebのUIテスト自動化 - Seleniumを使ってみる - Qiita")

    // Quitaの記事内の動画へのリンクをクリック
    clickLink("参考動画")

    Thread.sleep(35000)

    driver.quit()
  }

}

実行

最後に下記のコマンドを入力することで、Chromeが起動して一連の操作が自動的に実行されます。

$ sbt run

参考

Selenium WebDriver - Selenium Documentation
http://www.seleniumhq.org/docs/03_webdriver.jsp#how-does-webdriver-drive-the-browser-compared-to-selenium-rc

Scala + Selnium
http://tototoshi.hatenablog.com/entry/20120310/1331369601

[mac][ruby]seleniumchromeを使用する
https://blog.hello-world.jp.net/ruby/1651/