Nodejsを使ってSeleniumでChromeを動かす

# はじめに
Seleniumとは、WebアプリケーションのUIテストツール。
動的なサイトのスクレイピングツールとしても使われている。
今回はSeleniumをNode.jsで動かす方法を紹介する。

# ドキュメント（英語版）
https://seleniumhq.github.io/selenium/docs/api/javascript/index.html

# 使用方法
## 事前準備（Windowsの場合）

### インストール
```bash
npm i selenium-webdriver
```

### ChromeDriverのダウンロード
ChromeDriver – WebDriver for Chrome
https://sites.google.com/a/chromium.org/chromedriver/downloads
実行ファイルと同じパスに配置

## 基本操作
### ライブラリのインポート

```javascript
const webdriver = require('selenium-webdriver');
const { Builder, By, until } = webdriver;
```
良く使うBuilder, By, untilをここで定義

### 設定（Chromeの場合）

```javascript
const capabilities = webdriver.Capabilities.chrome();
capabilities.set('chromeOptions', {
    args: [
        '--headless',
        '--no-sandbox',
        '--disable-gpu',
        `--window-size=1980,1200`
        // other chrome options
    ]
});
```

#### Chrome Options
https://peter.sh/experiments/chromium-command-line-switches/

### ブラウザの立ち上げ
```javascript
const driver = await new Builder().withCapabilities(capabilities).build();
```

```javascript
// ブラウザを終了する
driver.quit();
```

※基本的に返り値がpromiseなので、async/awaitを使う

### ページ移動
```javascript
await driver.get('https://www.youtube.com/');
```

### 要素の特定
```javascript

// タグ名で特定
let el1 = await driver.findElement(By.tagName('hoge'));
let el2 = await driver.findElement({ tagName: 'hoge' });

// idで特定
let el1 = await driver.findElement(By.id('foo'));
let el2 = await driver.findElement({ id: 'foo' });

// classで特定
let el1 = await driver.findElement(By.className('bar'));
let el2 = await driver.findElement({ className: 'bar' });

// cssセレクタで特定
let el1 = await driver.findElement(By.css('#foo'));
let el2 = await driver.findElement({ css: '#foo' });

// 複数要素の特定
let els1 = await driver.findElements(By.css('.bar'));
let els2 = await driver.findElements({ css: '.bar' });

```

### 要素から属性の取得
```javascript

let el = await driver.findElement(By.css('#foo'));

// テキストを取得
let text = await el.getText();
// ↑だとなぜか動かない場合はこちら
let innerHTML = await el.getAttribute('innerHTML');

// その他属性の取得
let href = await el.getAttribute('href');

```

## 応用編

### 待ち処理
```javascript
/**
 * xxx：関数名
 * timeout：タイムアウト時間（ミリ秒）
 */
await driver.wait(until.xxx(), timeout);
```

#### 要素の出現
```javascript
// 要素の出現を待つ
await driver.wait(until.elementLocated(By.id('foo')), 10000);
await driver.wait(until.elementsLocated(By.className('bar')), 10000);
```

#### URL条件
```javascript
// 完全一致
await driver.wait(until.urlIs('xxx'), 10000);

// 部分一致
await driver.wait(until.urlContains('xxx'), 10000);

// 正規表現
await driver.wait(until.urlMatches(/xxx/), 10000);
```

#### タイトル条件
```javascript
// 完全一致
await driver.wait(until.titleIs('xxx'), 10000);

// 部分一致
await driver.wait(until.titleContains('xxx'), 10000);

// 正規表現
await driver.wait(until.titleMatches(/xxx/), 10000);
```

#### 特定要素のテキスト条件
```javascript
let el = await driver.findElement(By.css('#foo'));

// 完全一致
await driver.wait(until.elementTextIs(el, 'xxx'), 10000);

// 部分一致
await driver.wait(until.elementTextContains(el, 'xxx'), 10000);

// 正規表現
await driver.wait(until.elementTextMatches(el, /xxx/), 10000);
```

#### 要素の状態
```javascript
let el = await driver.findElement(By.css('#foo'));

// enabled
await driver.wait(until.elementIsEnabled(el), 10000);

// disabled
await driver.wait(until.elementIsDisabled(el), 10000);

// selected
await driver.wait(until.elementIsSelected(el), 10000);

// not selected
await driver.wait(until.elementIsNotSelected(el), 10000);

// visible
await driver.wait(until.elementIsVisible(el), 10000);

// not visible
await driver.wait(until.elementIsNotVisible(el), 10000);

```

### スクリーンショットを撮る
```javascript
// スクリーンショット画像をbase64でエンコードしたもの
let base64 = await driver.takeScreenshot();

// bufferに変換
let buffer = Buffer.from(base64, 'base64');
```

## サンプル
Youtubeのスクリーンショットを撮るサンプル

```javascript:sample.js
const fs = require('fs');
const { promisify } = require('util');
const webdriver = require('selenium-webdriver');
const { Builder, By, until } = webdriver;

const capabilities = webdriver.Capabilities.chrome();
capabilities.set('chromeOptions', {
    args: [
        '--headless',
        '--no-sandbox',
        '--disable-gpu',
        `--window-size=1980,1200`
    ]
});

// awaitを使うので、asyncで囲む
(async () => {

    // ブラウザ立ち上げ
    const driver = await new Builder().withCapabilities(capabilities).build();

    // Youtubeへ移動
    await driver.get('https://www.youtube.com/');

    // 検索ボックスが表示されるまで待つ
    await driver.wait(until.elementLocated(By.id('search')), 10000);

    let base64 = await driver.takeScreenshot();
    let buffer = Buffer.from(base64, 'base64');

    // bufferを保存
    await promisify(fs.writeFile)('screenshot.jpg', buffer);

    // ブラウザ終了
    driver.quit();

})();
```