electronでpuppeteerを使ってみることにした
すでに社内で、electronで作成した座席表アプリを配布済みです。この環境下でpuppeteer + nexeでバイナリ配布をするのも良いのですが、すでにある座席表アプリでpuppeteerを動かせないかな?と思い挑戦しました。
ただし、keytarも使わなければ行けない環境であるため、これまでのelectron 3.0.0 + keytar 4.2.1の環境では古すぎてpuppeteerが動きません(Error : Protocol errorが出て、getBrowserContexts wasn't foundと出ます)。そこで、今回electronを5.0にし、keytarを4.6.0でビルドし直した所うまく動いたので、これを元に進めたいと思います。
目次
今回使用するモジュール等
ちなみに、あえてpuppeteer-in-electronを使わずとも、puppeteer-coreのみでインストール済みのChromeを起動させて、Chromeを操縦することは出来るので、puppeteer-in-electronを使わなくとも問題なく構築は可能です(実際自分は、puppeteer-coreのみでelectronからChromeを操縦させてます。
事前準備
Keytarのインストール
Node.jsはv10.15.1を利用しています。リビルド環境の構築はWindows向けで構築しています。electron-rebuildは最新版に入れ直しています。keytarのインストールは以下のような手順です。
1 2 3 4 5 6 |
npm i keytar@4.6.0 cd node_modules cd keytar node-gyp configure cd ..¥..¥ electron-rebuild -w -keytar -p -f |
Puppeteerをインストール
electron自体がChromiumなので、別途Chromiumは必要ありません。ということで、今回もpuppeteer-coreを利用します。また、electronのBrowserWindow内で使いたいので、puppeteer-in-electronもインストールします。
1 2 |
npm i puppeteer-core npm i puppeteer-in-electron |
electron v5.0.0はpuppeteerから操作が可能なので、今回のようなアプリを作る場合には、electronのバージョンは5以上推奨です(ただし、keytarを使うとなると簡単に最新版という事にはいかないのが残念)。
ソースコード
Puppeteer単体での場合
puppeteer + node.jsとは異なりpuppeteer-in-electronの場合冒頭の部分が少々異なります。その部分以降はこれまでのawaitから始まる命令文をつなげていけば良いので、そこだけ要注意です。また今回は、keytarとの併用ができるかの確認の為に、main()につなげて無駄にパスワードの保存と取り出しを記述しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
//モジュールの読み込み const {BrowserWindow, app} = require("electron"); const pie = require("puppeteer-in-electron") const puppeteer = require("puppeteer-core"); const keytar = require('keytar'); //puppeteerで操作するメインの処理 const main = async () => { //puppeteer-in-electronに接続 const browser = await pie.connect(app, puppeteer); //URLを指定してBrowserWindowに対してページの移動をさせる const window = new BrowserWindow(); const url = "https://officeforest.org/wp/"; await window.loadURL(url); //viewportのサイズの指定 const viewportHeight = 800; const viewportWidth = 1280; //ページを表示する const page = await pie.getPage(browser, window); //ここから通常のpuppeteerと同じような記法になります。 await page.setViewport({ width: viewportWidth, height: viewportHeight }); //スクリーンショットを撮って保存する await page.screenshot({ path: './result.png', fullPage: true}) //BrowserWindowを閉じる await window.destroy(); }; //サービス名を構築 var servicename = "zaseki_sakura"; //puppeteerを動かす main() .then(function(){ //パスワードを保存する keytar.setPassword(servicename,"sakura","password"); console.log("OK") return "sakura"; }) .then(function(data){ const secret = keytar.getPassword(servicename,data); secret.then((result) => { console.log(result); return; }); }) |
- main関数がpuppeteerで自動操縦させる関数になります。
- main関数冒頭のpuppeteerを起動する部分が異なります。pie.connect(app, puppeteer);にてbrowserを宣言
- executablePathなどは不要です。BrowserWindowがそれを担います。
- page.gotoは冒頭は不要です。BrowserWindowに対してloadUrlを指定でロードさせればOK
- pie.getPage以降は通常のpuppeteerでの記法と同じになります。
- Windowを閉じる場合には、electronなのでwindow.destroy()で閉じればOKです。
- main関数につづけて、promiseにてkeytarによる資格情報マネージャへのパスワードの格納/取得を追加して動作確認しています。
- electron 5.0.0 + keytar 4.6.0での動作確認が取れましたので、今後はこのバージョンを基準に開発していきたいと思います。
他のアプリ内に取り込んで使う場合
puppeteer-in-electronを使う上でちょっと厄介だったのが、既存のアプリに組み込んで使いたい場合。単体と違い、他のコードが存在し、それが原因でエラーが出たりします。特に厄介なのは、Must be called at startup before the electron app is ready.というエラー。app.onのreadyよりも前に、browserについて初期化しておいて、puppeteerに接続させておかなければなりません。よって、以下のような形で記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
//puppeteerモジュール読み込み const pie = require("puppeteer-in-electron") const puppeteer = require("puppeteer-core"); var browser = null; var page = null; //app.onより前にpuppeteerに接続させる const main = async () => { browser = await pie.connect(app, puppeteer); }; main(); //appの初期化 app.on('ready', () => { }); //puppeteerで操作する async function openform(tempid,frmname,frmmail){ const window = new BrowserWindow({ 'width': 950, 'height': 750, 'autoHideMenuBar':true, webPreferences: { nodeIntegration: false, contextIsolation: false, webSecurity: false }, 'resizable':true, 'fullscreenable':false, 'fullscreen':false, 'alwaysOnTop':false, 'modal':true, 'parent': mainWindow //モーダル表示にしておく }); const url = "コントロールするサイトのURL"; await window.loadURL(url); page = await pie.getPage(browser, window); //viewportをフルサイズにする await page._client.send('Emulation.clearDeviceMetricsOverride'); //自動入力 await page.waitForSelector('.ui > .row:nth-child(1) > .column > .el-input > .el-input__inner') await page.click('.ui > .row:nth-child(1) > .column > .el-input > .el-input__inner') await page.type('.ui > .row:nth-child(1) > .column > .el-input > .el-input__inner', tempid) await page.waitForSelector('.ui > .ui > .ui > .row:nth-child(1) > .column') await page.click('.ui > .ui > .ui > .row:nth-child(1) > .column') await page.waitForSelector('.ui > .row:nth-child(2) > .column > .el-input > .el-input__inner') await page.click('.ui > .row:nth-child(2) > .column > .el-input > .el-input__inner') await page.type('.ui > .row:nth-child(2) > .column > .el-input > .el-input__inner', frmname) await page.type('.ui > .row:nth-child(3) > .column > .el-input > .el-input__inner', frmmail) } |
- app.onのready前にbrowserとpageを宣言しておいて、browserに対してconnectさせて置かなければならない。
- フォームに入力させる場合は、自動化よりも前に予め入力予定のワードはどこかで取得させておくとスムーズです。
- kintoneのフォームブリッジは、ちょっと変な仕様で、直接機械的にinputに対して値をねじ込むと、入っていても送信時に「入っていない」と言われるので、このようにpuppeteerにて入力させ、keydownイベントを発火させる必要があります。
- viewportを常にウィンドウサイズに合わせるには、await page._client.send('Emulation.clearDeviceMetricsOverride');にて実現可能です。
- tempid, frmname, frmmailは予め自分の場合、MySQLから取得したものを格納してあります。
- BrowserWindowが開かれるとPuppeteerが発動し、社員番号と名前、メールアドレスがフォームに自動入力されます。
- webviewタグ表示の対象に対して、javascriptでキーコードを送っても反応しないので、Puppeteerでページ表示させて操作する方法がもっともベターです。
jQueryを使えるようにする
Puppeteerの非常に優れている点は、既存のフォームなどに対して後から、jQueryなどのモジュールを読み込ませて、機能を付け足すことが出来る点です。例えば、formブリッジのテキストボックスに対して、jQueryのAutocompleteを付け足すことで、フォームでの入力補助機能をアップさせたりなどなど。
今回、GoogleのCDNで配布されているライブラリを用いて、formブリッジのテキストボックスにautocompleteを設定してみました。
※Autocompleteは文字列でなければいけないので、xlsxモジュールなどでデータを生成する場合、String()で型変換しておくことが必要です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
・・・・前略・・・・ //外部ライブラリ挿入 await page.addScriptTag({url: 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js'}); await page.addScriptTag({url: 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js'}); await page.addStyleTag({url: 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css'}); await page.addStyleTag({path: 'css/autocomp.css'}); //autocomplate const _tmp = await page.evaluate(() => { var data = [ {label:'しいたけ', value:'2012'}, {label:'しめじ', value:'2013'}, {label:'まつたけ', value:'2014'}, {label:'なめこ', value:'2015'}, {label:'えのきだけ', value:'2016'}, {label:'たもぎたけ', value:'2017'}, ]; //formに対してNAME属性を加える var forms = document.forms[0].setAttribute("name","kinoko"); //4つ目の項目に対してid属性を加える document.kinoko.elements[3].setAttribute("id","salad"); //autocomplateを加える $('#salad').autocomplete({ source: data, autoFocus: true, delay: 500, minLength: 2 }); }); |
- addScriptTagにてURL指定でスクリプトライブラリを挿入可能です
- addStyleTagにてURL指定でスタイルシートを挿入可能です。
- page.evaluateにて、任意のスクリプトを対象のページに対して実行させることが可能です。
- あとは、通常のHTML上で記述するようにJavaScriptを記述すればOK.
- evaluate内に外側の変数を渡したい場合には、以下のように引数で渡すと利用すことが可能になります。
- xlsxモジュールなどで、固定場所にあるexcelデータからautocomplete用のデータを生成しておき、引数でevaluateに渡せば最新のリストをもってautocompleteが反映可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
var data = [ {label:'しいたけ', value:'2012'}, {label:'しめじ', value:'2013'}, {label:'まつたけ', value:'2014'}, {label:'なめこ', value:'2015'}, {label:'えのきだけ', value:'2016'}, {label:'たもぎたけ', value:'2017'}, ]; //data変数を引数として渡し、evaluate内で利用する const _tmp = await page.evaluate((data) => { //formに対してNAME属性を加える var forms = document.forms[0].setAttribute("name","kinoko"); //4つ目の項目に対してid属性を加える document.kinoko.elements[3].setAttribute("id","salad"); //autocomplateを加える $('#salad').autocomplete({ source: data, autoFocus: true, delay: 500, minLength: 2 }); },data); |
図:formブリッジに対してautocompleteが反映出来た
単一実行ファイルを作成する
Node.js 18よりSingle executable applicationsという機能が装備され、標準で単独実行ファイルが作成できるようになりました。結果pkgはプロジェクト終了となっています。よって、以下のエントリーの単一実行ファイルを作成するを参考に、Node18以降はexeファイルを作成することが可能です。
関連リンク
- build problems with electron 5 beta #173
- 【node.js入門】Promiseによる非同期処理の使い方まとめ!
- Electron Vue + Puppeteerで作る便利デスクトップApp
- Electron x Puppeteer
- Electronとpuppeteerを用いてRadikoをMP3化するソフトを製作しました
- CarloでWeb制作者が手軽にデスクトップアプリを作る
- jQueryのAutoCompleteを使って自動補完される検索ボックスを作る方法
- JavaScriptの「match」について
- jQuery UI Autocompleteでかなを入力した際に漢字を表示
- jQueryUI の Autocomplete でサジェスト表示と選択時の表示を変える方法