いろいろな言語でSeleniumを実行してみる（Node.js、TypeScript、CoffeeScript編）

# 前置き

いろいろな言語でSeleniumを最低限動かせるようになってみようという勉強のメモです。
今回はNode.js、TypeScript、CoffeeScriptを試してみました。
書き手は普段RubyでSeleniumを使うことが多く、ときどきVB、Javaの他、C#やScalaで書くこともあります。（[Scalaに入門したときに書いた記事](https://qiita.com/marumaru/items/8e0d320a723daf380f97)）
Node.jsは多少読み書きの経験あり、TypeScriptは初めまして、CoffeeScriptはだいぶ昔に軽く勉強した程度です。

このメモでは、Macへの各環境のインストールから、SeleniumでHeadless Chromeを操作してQiitaのサイトへアクセスし、キーワード "selenium" で検索してすべての検索結果数のバッジの値を取得するコードの実行までの手順をまとめます。

![SampleCapture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79324/a6f67d3f-58bf-c6ce-63a0-ae47e7ee42e5.png)
※画面は2019/4/21時点のものです。

# 環境

* OS: Mac OS X 10.14.3 Mojave
* Homebrew: 2.1.4-67-gc95cf90
* nodebrew: 1.0.1
* Node.js: v12.4.0
* npm: 6.9.0
* TypeScript: 3.5.1
* CoffeeScript: 2.4.1
* selenium-webdriver: 4.0.0-alpha.1
* @types/selenium-webdriver: 4.0.0
* Google Chrome: 75.0.3770.80（Official Build）
* ChromeDriver: 75.0.3770.8

Homebrewの環境は構築済みであることを前提とします。

# 準備

## ChromeDriverのインストール

Homebrewでchromedriverをインストールします。

```bash:コマンド
$ brew tap homebrew/cask
$ brew cask install chromedriver
$ chromedriver -v
ChromeDriver 75.0.3770.8 (681f24ea911fe754973dda2fdc6d2a2e159dd300-refs/branch-heads/3770@{#40})
```

以前はbrew installでchromedriverをインストールできましたが、現在はcaskを使うようになっています。

## Node.jsのインストール

今回はNode.jsのバージョン管理にnodebrewを使用します。
Homebrewでnodebrewをインストール後、nodebrewでNode.jsをインストールします。
npmも最新の状態にしておきます。

```bash:コマンド
$ brew install nodebrew
$ nodebrew setup
$ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile
$ source ~/.bash_profile
$ nodebrew install stable
$ nodebrew use stable
$ node -v
v12.4.0

$ npm install -g npm
$ npm -v
6.9.0
```

# 各言語でSeleniumを実行

## Node.js

### selenium-webdriverのインストール

npmでselenium-webdriverをインストールします。

```bash:コマンド
$ mkdir ~/selenium-nodejs; cd $_
$ npm init -y
$ npm install -D selenium-webdriver
```

### ソースコードの作成

jsファイルを作成し、コードを書いていきます。

```bash:コマンド
$ touch test.js
```

```js:test.js
const { Builder, By, Capabilities, Key, until } = require('selenium-webdriver')
const capabilities = Capabilities.chrome()
// Chrome起動時のオプションを指定
capabilities.set('chromeOptions', {
  args: [
    '--headless',
    '--disable-gpu',
    '--window-size=1024,768'
  ],
  w3c: false
})

const search = async (query) => {
  // オプション指定ありでChromeを起動
  const driver = await new Builder()
    .withCapabilities(capabilities)
    .build()
  try {
    // Qiitaのトップページへアクセス
    await driver.get('https://qiita.com/')
    // 検索欄にキーワードを入力してEnter
    await driver
      .wait(until.elementLocated(By.name('q')), 5000)
      .sendKeys(query, Key.RETURN)
    // 検索結果数のバッジの値を取得
    const result = await driver
      .wait(until.elementLocated(By.className('badge')), 5000)
      .getText()
    // キーワードと結果の値を出力
    console.log(`${query}: ${result}`)
  } catch(e) {
    console.log(e)
  } finally {
    // Chromeを終了
    driver && await driver.quit()
  }
}

search('selenium')
```

最低限動くコードとしては、このような感じでしょうか。
Selenium公式のドキュメントにもサンプルコードはありますが書き方が古いので、なるべくモダンなJavaScriptっぽくなるように調べながらコーディングしてみました。[^セミコロン]
改善の余地はまだまだありますが、それについてはまた別の記事に譲ろうと思います。

[^セミコロン]: セミコロンを省略したのは単に書き手がRubyの文化に染まっているためであって他意はありません。

### ソースコードの実行

```bash:コマンド
$ node test.js
selenium: 2465
```

2465件[^件数更新]という結果が出力されました。

[^件数更新]: 2019/6/8時点の値です。前置きの2019/4/21時点のスクリーンショットよりも件数が増えています。

## TypeScript

### typescriptとselenium-webdriverのインストール

npmでtypescriptとselenium-webdriverをインストールします。

```bash:コマンド
$ mkdir ~/selenium-typescript; cd $_
$ npm init -y
$ npm install -D typescript selenium-webdriver @types/selenium-webdriver
```

### ソースコードの作成

tsファイルを作成し、コードを書いていきます。

```bash:コマンド
$ touch test.ts
```

```typescript:test.ts
import { Builder, By, Capabilities, Key, until, WebDriver } from 'selenium-webdriver'
const capabilities: Capabilities = Capabilities.chrome()
capabilities.set('chromeOptions', {
  args: [
    '--headless',
    '--disable-gpu',
    '--window-size=1024,768'
  ],
  w3c: false
})

async function search(query: string): Promise<void> {
  const driver: WebDriver = await new Builder()
    .withCapabilities(capabilities)
    .build()
  try {
    await driver.get('https://qiita.com/')
    await driver
      .wait(until.elementLocated(By.name('q')), 5000)
      .sendKeys(query, Key.RETURN)
    const result: string = await driver
      .wait(until.elementLocated(By.className('badge')), 5000)
      .getText()
    console.log(`${query}: ${result}`)
  } catch(e) {
    console.log(e)
  } finally {
    driver && await driver.quit()
  }
}

search('selenium')
```

### tsconfigの作成

コンパイルの設定ファイルを作成します。

```bash:コマンド
$ npx tsc --init
```

### ソースコードのコンパイルと実行

```bash:コマンド
$ npx tsc --target es6
$ node test.js
selenium: 2465
```

Node.jsとほとんど同じコードで動かせました。
この程度の内容ですとNode.jsとTypeScriptのどちらを使っても大差ない印象ですが、テストフレームワークを使ったり、デザインパターンを適用するなどしてコードの規模が大きくなってくると、TypeScriptのメリットも活きてくるのだろうと思います。

## CoffeeScript

### coffeescriptとselenium-webdriverのインストール

npmでcoffeescriptとselenium-webdriverをインストールします。

```bash:コマンド
$ mkdir ~/selenium-coffeescript; cd $_
$ npm init -y
$ npm install -D coffeescript selenium-webdriver
```

### ソースコードの作成

coffeeファイルを作成し、コードを書いていきます。

```bash:コマンド
$ touch test.coffee
```

```coffeescript:test.coffee
{ Builder, By, Capabilities, Key, until: Until } = require 'selenium-webdriver'
capabilities = Capabilities.chrome()
capabilities.set('chromeOptions', {
  args: [
    '--headless',
    '--disable-gpu',
    '--window-size=1024,768'
  ],
  w3c: false
})

search = (query) ->
  driver = await new Builder()
    .withCapabilities(capabilities)
    .build()
  try
    await driver.get 'https://qiita.com/'
    await driver
      .wait(Until.elementLocated(By.name('q')), 5000)
      .sendKeys(query, Key.RETURN)
    result = await driver
      .wait(Until.elementLocated(By.className('badge')), 5000)
      .getText()
    console.log "#{query}: #{result}"
  catch e
    console.log e
  finally
    driver && await driver.quit()

search 'selenium'
```

requireする際にuntilがCoffeeScriptの予約語になっている点に注意が必要です。

### ソースコードのコンパイルと実行

```bash:コマンド
$ npx coffee -c test.coffee
$ node test.js
selenium: 2465
```

Rubyライクな言語ということもあって個人的には親しみやすいです。
斜陽の時代を迎えている感の強いCoffeeScriptですが、リバイバルしないものでしょうか。[^CoffeeScript]

[^CoffeeScript]: 実務ではなかなか難しそうですが、趣味のプログラミングでもっと触ってみたいと思いました。

---
# 参考サイト
ソースコード作成にあたり、以下のサイトの記事を特に参考に致しました。
ありがとうございました。

* [イマドキのJavaScriptの書き方2018 - Qiita](https://qiita.com/shibukawa/items/19ab5c381bbb2e09d0d9)
* [最近のselenium-webdriverの話 - Qiita](https://qiita.com/nazomikan/items/61143cbf01274c05b654)
* [Nodejsを使ってSeleniumでChromeを動かす - Qiita](https://qiita.com/tonio0720/items/70c13ad304154d95e4bc)
* [TypeScript公式ハンドブック](https://www.typescriptlang.org/docs/handbook/basic-types.html)
* [CoffeeScript公式リファレンス](https://coffeescript.org/#language)
* [Selenium＋Chrome75で全てのオプションが効かない - Qiita](https://qiita.com/MasamotoMiyata/items/079dcc9d497586dbb8ad)