クソつまらないWebブラウザを使う事務作業をPuppeteerでヘッドレスChromeを使ってバッチ化

# 愚痴
　テスト自動化とか定形作業自動化とかで**ヘッドレスChrome**に対して**Puppeteer**でサクッと行うことが増えてきました。

　決まった日に社内イントラネット上のとあるWebアプリケーションのクソ面倒くさい操作のGUIを操って帳票作成のスケジュール設定。さらに別の決まった日に作成された帳票をこれまたクソ面倒くさくてつまらないクソGUIを操作してダウンロード。特定のフォルダに保存する。そんなクソみたいで「シット！」と舌打ちしたくなるような定型事務作業があります。
　
　これが実に筆舌に尽くしがたいほど個人的に特にクソつまらないのです。そもそもマウスを使ってちんたら画面操作をしたくないのです。本来イントラネット上のとあるWebアプリケーションがすべてバッチでやってくれればよいのです。GUIで人間にやらせようとする魂胆があざとい。

　今までは私の担当分はSelenium Webdriverを使ってしのいできました。しかし、ブラウザの画面GUIが自己主張が強すぎてうっとうしいものです。

　でも、**ヘッドレスChrome**なら裏で静かにこっそりつまらない定型事務作業をもくもくと地下労働者のように行ってくれます。これはなかなか楽しい。例えて言うなら『王侯貴族が奴隷をこき使って優雅に暮らす』そんな主従関係の現代IT版みたいな感じでしょうか・・・👈たとえが悪すぎます・・・(-_-メ)

　そんなクソ定型作業も世の流行なのでRPAで自動化とかいうことになってRPAツールにクソつまらない設定パラメータを与えてロボ吉で動かすようになってきました。クソつまらない単なる作業はロボ吉にまかせる。それはそれで素晴らしいことです。

　でもでも、このRPAなるロボ吉。いかにも「私やってます。ニカッ( ^)o(^ )」的な自己主張が強すぎてどうにも腹立たしい。少しイラッとするのです。「こちとら、裏でこっそりバッチ君が好きなんじゃあ~~（笑）」。ということで**Puppeteer**で**ヘッドレスChrome**する。個人的にツボです。

　そこで、個人的なメモを以下に書きなぐります。間違いや勘違いが多々あってもとりあえず、ここにしばらくメモリます。

# Install

|Seq.|すること|URL / コマンド|備考|
|-------|-------|---|----|
|1|**Git Bash**（Gitの**MINGW**）をInstallする|https://gitforwindows.org/|[Download]ボタンを押す|
|2|**Node.js**をInstallする|https://github.com/nullivex/nodist/releases|Windows：NodistSetup-v0.8.8.exe<br/>Linux/MacOS：Source code(tar.gz)|
|3|**Git Bash**（**MINGW**）を起動|||
|4|**Node.js**のバージョンを確認|$ nodist -v ||
|5|パッケージ管理ツール**npm**のバージョンを確認|$ npm -v |vX.X.X形式で表示される|
|6|パッケージ管理ツール**Yarn**のInstall|$ npm install -g yarn --scripts-prepend-node-path|Facebook発のパッケージ管理ツール**Yarn**を**npm**を使ってInstall|
|7|**Node.js**のInstall可能なバージョン一覧を確認する|$ nodist dist||
|8|**Node.js**の最新のLTS(Long-term Support)を確認する|https://nodejs.org/ja/|[XX.XX.X LTS 推奨版]を確認|
|9|**Node.js**をバージョンアップする|$ nodist 10.15.2 |Installing 10.15.2<br/>Installation successful.<br/>と表示されれば成功|
|10|プロジェクト用のディレクトリを作成|$ mkdir kusoprj|$cd kusoprj|
|11|package.jsonを作成|$ winpty yarn.cmd init -y|yarn init vX.X.X<br/>と表示される|
|12|PuppeteerのInstall|$ yarn add puppeteer||


```json:package.json(作成直後)
{
  "name": "sample",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT"
}
```
```json:package.json(Puppeteerのインストール後)
{
  "name": "sample",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "puppeteer": "^1.12.2"
  }
}
```

# 基本Sequence

|Seq.|すること|備考|
|-------|-------|----|
|1|Puppeteer利用設定|
|2|エミュレート対象デバイス指定設定|省略可<br/>デバイスをエミュレートする場合のみ必要|
|3|エミュレート対象デバイスの指定|省略可<br/>デバイスをエミュレートする場合のみ必要|
|4|非同期のラムダ関数定義||
|5|ブラウザオブジェクト生成||
|6|新規ページ生成||
|7|エミュレート対象デバイスを呼び出し|省略可<br/>デバイスをエミュレートする場合のみ必要|
|8|ページの遷移||
|9|ページへの操作||
|10|ブラウザオブジェクトを閉じる||

```JavaScript:Puppeteer利用基本シーケンスの例
const puppeteer = require('puppeteer');
//const devices = require('puppeteer/DeviceDescriptors');
//const iPhone = devices['iPhone X'];
(
  async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    //await page.emulate(iPhone);
    await page.goto('https://www.tesla.com/jp/');

    await page.screenshot( { path: 'teslaHomePage.png', fullPage: true } );

    await browser.close();
  }
)();
```
# 実行

どこの会社にもあるWindows10 Pro クソ事務作業用端末に、あらかじめNode.jsとPuppeteerライブラリ、そうして実行プラットフォームであるGit-Bash（古くからあるMINGW）を仕込んでおく

+ Git-Bash(MINGW64)起動

+ スケジュール実行
最初のうちは手打ち。安定してきたら定期定時バッチ処理に移行。

```bash
$ cd '//fileserver/クソ帳票/Tool'
$ node schedule_kuso.js
```
+ ダウンロード実行
最初のうちは手打ち。安定してきたらこちらもダウンロード日にバッチ処理に移行。

```bash
$ cd '//fileserver/クソ帳票/Tool'
$ node download_kuso.js
```

# 外部操作
|操作|操作対象オブジェクト|メソッド|パラメータ|
|----|------------------|--------|---------|
|スクリーンショット|page|screenshot()|JSONオブジェクト|
|URL遷移|page|goto()|URL|

##### JSONオブジェクト
|意味|パラメータ名|値|
|----|-----------|------|
|スクリーンショットファイルの保存パス|path|パス|
|全画面取得|fullPage|する：true<br/>しない：false|
## 
```JavaScript:スクリーンショット
await page.screenshot( { path: 'hoge.png', fullPage: true } );
```
```JavaScript:URL遷移
await page.goto('https://www.tesla.com/jp/');
```

# 認証
|種類|操作対象オブジェクト|メソッド|パラメータ1|
|----|------------------|--------|---------|
|BASIC認証|page|setExtraHTTPHeaders()|JSONオブジェクト|
##### JSONオブジェクト
|意味|パラメータ名|値|
|----|-----------|------|
|認証の種類|Authorization|\`Basic ${new Buffer(`${ユーザ定数}:${パスワード定数}`).toString(認証方式定数}`|
```JavaScript:BASIC認証
await page.setExtraHTTPHeaders(
  {
     Authorization: 
     `Basic ${new Buffer(`${'Hoge Foo'}:${'somepass'}`).toString('base64')}`
  }
);
```

# DOM操作
|操作|操作対象オブジェクト|メソッド|パラメータ1|パラメータ2|
|----|------------------|--------|---------|----------|
|実行ボタンクリック|page|click()|input[type="submit"]|-|
|アンカークリック|page|click()|a[href^="遷移先指定の最初の文字列"]|-|
|特定要素クリック|page|$$eval()|selector|タグ名 => タグ名.filter(<br/>タグ名 => タグ名.textContent === '要素内文字列'<br/>))[0].click() }|
|値入力|page|type()|タグ名[属性="属性値"]|入力値|
|プルダウンメニュー選択|page|select()|#selectタグのid名|optionタグのvalue属性|
|プルダウンメニュー選択|page|select()|#select[name="name属性名"]|optionタグのvalue属性|
|ラジオボタン選択|page|click()|#inputタグのid名|-|

```JavaScript:実行ボタンクリック
<input type="submit" value="SEND">
.......
await page.click( 'input[type="submit"]' );
```
```JavaScript:アンカークリック
<a href=index.jsp?user=hogekun>Click!</a>
.......
await page.click( 'a[href^="index.jsp?user="]' );
```
```JavaScript:特定要素クリック
<div class="maker">
    <button>Click Me!</button>
    <a href="index.php?maker=Tesla">Click Me!</a>
    <div>Please Click upper 2 items!</div>
</div>
.......
(
  await page.$$eval(
     selector,
     a => a.filter(
        a => a.textContent === 'Click Me!'
     )
  )
)[0].click();
```
```JavaScript:値入力
<input type="text" name="hoge" id="baa">
.......
await page.type( 'input[name="hoge"]', 'Hoge' );
await page.type( 'input[id="baa"]', 'Baa' );
```
```JavaScript:プルダウンメニュー
<select id="maker" name="maker">
  <option value="Tesla">Tesla</option>
  <option value="TOYOTA">TOYOTA</option>
  <option value="BMW">BMW</option>
</select>
.......
await page.select('#maker', 'Tesla');
await page.select('select[name="maker"]', 'BMW');
```
```JavaScript:プルダウンメニュー
<input type="radio" name="maker" value="Tesla" id="Tesla">Tesla
<input type="radio" name="maker" value="TOYOTA" id="TOYOTA">TOYOTA
<input type="radio" name="maker" value="BMW" id="BMW">BMW
.......
await page.click('#Tesla');
```
## 追記

　あれから社内でWebブラウザを使うさまざまなクソつまらない定型事務作業をnode.jsサーバ上のExpressフレームワーク内でGoogle **Puppeteer**君を動かすことでバッチ処理化してきました。結果、大満足です(^^♪←昭和のおじさん風顔文字・・・汗；；。

　バッチ処理がなじまない事務作業は、オンプレミスのnode.jsサーバをサクっと立ててWebアプリケーション化しました。

　たとえば、グループ会社内情報共有掲示板の数十ある部署別スレッドに月次定型的な「みんな大好きエクセル帳票君」を定型文とともに投稿するなんていう「クソつまらない」事務「作業」はWebアプリケーションの画面にあるボタンをポチっと押すだけで、サーバーサイドでWebブラウザを一切表示せずに、黙々とバッチ的に即時実行してしまいます。

 Google **Puppeteer**君半端ねぇ～(^_-)-☆。単純労働、事務大嫌いで、エクセル君キライなITエンジニアの味方　**Puppeteer**君　サイコウ！