2019/10/26

Electron のデフォルトでは Spectron が正しく動作しない

@Electron 7.0.0 @Spectron 9.0.0

electron-quick-startmochaspectron をインストールして、簡単なテストを走らせてみました。 すると、以下のようなエラーが。

  1) Application launch
       shows an initial window:
     TypeError: waitUntilWindowLoaded Cannot read property 'isLoading' of undefined
      at waitUntil(<function>, ) - application.js:263:17

実際に走らせたのは以下のコードです。

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
const Application = require('spectron').Application
const assert = require('assert')
const electronPath = require('electron')
const path = require('path')
 
describe('Application launch', function () {
  this.timeout(10000)
 
  beforeEach(function () {
    this.app = new Application({
      path: electronPath,
      args: [path.join(__dirname, '..')]
    })
    return this.app.start()
  })
 
  afterEach(function () {
    if (this.app && this.app.isRunning()) {
      return this.app.stop()
    }
  })
 
  it('shows an initial window', function () {
    // 以下の waitUntilWindowLoaded() 内で落ちてる
    return this.app.client.waitUntilWindowLoaded().getWindowCount().then(function (count) {
      assert.equal(count, 1)
    })
  })
})

waitUntilWindowLoaded() 内で問題が起こっているようですが、このコードは Spectron の公式ページに書かれているものです。 waitUntilWindowLoaded() の使用方法に問題があるとは思えません1

Electron の設定が原因

この問題、実は Spectron の README.md にしれっと書いてありました。

The Electron helpers provided by Spectron require accessing the core Electron APIs in the renderer processes of your application. So, either your Electron application has nodeIntegration set to true or you'll need to expose a require window global to Spectron so it can access the core Electron APIs.

nodeIntegrationfalse だと Spectron は正しく動かないとのこと。

そして、この nodeIntegration、Electron 5 以降ではデフォルト false なのです。(理由は後述)

このことは、公式の Issue#174 にもあがっていて、他にも app.electronapp.webContentsundefined になってしまう問題があるようです。 実際、app.webContents.executeJavaScript('1 + 2') を呼んでみたところ、エラーになりました。

  1) Application launch
       shows an initial window:
     TypeError: Cannot read property 'executeJavaScript' of undefined
      at Context.<anonymous> (test/spec.js:24:35)
      at <anonymous>
      at process._tickDomainCallback (internal/process/next_tick.js:228:7)

対策

nodeIntegrationtrue にすれば動作します。 しかし、常に true にしてしまうとセキュリティリスクがあるので、以下のようにテストの時だけ true になるようにするのが良いかと思います。

1
2
3
4
5
6
mainWindow = new BrowserWindow({
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
    nodeIntegration: (process.env.NODE_ENV === 'test')
  }
})

私の環境では NODE_ENV 環境変数は自動で test に設定されました。 ただ、これは環境依存だと思うので package.json に以下のように書いておくと安心です。

package.json
1
2
3
"scripts": {
  "test": "NODE_ENV=test mocha"
},

私の環境では公式に載っている preload.js 内で window.electronRequire を設定する方法は動きませんでした。何でだろう…

なぜ、デフォルト false になったのか

nodeIntegrationtrue だと Renderer プロセスの中から Node.js の API にアクセス出来るようになります。 つまり、Renderer からローカルのファイルやその他のローカルリソースに直接アクセスできてしまうのです。

もし、この状態で悪意のある外部コンテンツを実行すると、とても危険です。 そのため、nodeIntegration はデフォルト false になったのだと思われます。

セキュリティを高めたのは良いけれど、テスト時と実行時の動作環境があまりに異なってしまうのはどうなんだろう…

  1. 公式に書いてあっても、ドキュメントが更新されてなく使えないということは多々あるけど… 
?

0 件のコメント: