7

I am using just the basic Spectron test file (in Typescript) to open my app, get the window count, and presumably exit. However, Spectron's app.stop() only seems to close the dev tools window and leaves the main window running. I've searched around and come across a few GitHub issues with people having this problem. The best people seem to offer is to use pkill. I don't want to do that as it could potentially kill more than it should (on a CI server, for example).

Before I show all the code, my question is, what do I need to do to make Spectron's session actually exit after a test?

Here's my spec.ts containing my tests:

import { Application } from "spectron";
import * as assert from "assert";
import * as electronPath from "electron";
import * as path from "path";

describe('Application launch', function () {
    this.timeout(10000);

    beforeEach(function () {
        this.app = new Application({
            path: electronPath,
            args: [path.join(__dirname, '..')]
        } as any);
        return this.app.start();
    })

    afterEach(function () {
        if (this.app && this.app.isRunning()) {
            // TODO: figure out way to close all windows
            return this.app.electron.app.quit();
        }
    });

    it('shows an initial window', function () {
        return this.app.client.getWindowCount().then(function (count: any) {
            //assert.equal(count, 1)
            // Please note that getWindowCount() will return 2 if `dev tools` are opened.
            assert.equal(count, 2);
        });
    });
});

Here's my package.json:

{
  "main": "dist/js/entry/main.js",
  "scripts": {
    "build": "node_modules/.bin/tsc -p tsconfig.json && mkdir -p dist/static && rsync -ar --delete static/ dist/static/",
    "lint": "node_modules/.bin/tslint -c tslint.json -p tsconfig.json",
    "start": "node_modules/.bin/electron .",
    "build_start": "npm run build && npm start",
    "package": "node_modules/.bin/electron-builder",
    "package-test": "node_modules/.bin/electron-builder --dir",
    "test": "node_modules/.bin/mocha -r ts-node/register -r ignore-styles -r jsdom-global/register test/*.ts"
  },
  "devDependencies": {
    "@types/chai": "^4.1.4",
    "@types/mocha": "^5.2.4",
    "ajv": "^6.5.1",
    "asar": "^0.14.3",
    "chai": "^4.1.2",
    "electron": "^2.0.3",
    "electron-builder": "^20.16.0",
    "ignore-styles": "^5.0.1",
    "jasmine": "^3.1.0",
    "jsdom": "^11.11.0",
    "jsdom-global": "^3.0.2",
    "mocha": "^5.2.0",
    "spectron": "^3.8.0",
    "ts-node": "^7.0.0",
    "tslint": "^5.10.0",
    "typescript": "^2.9.2"
  },
  "build": {
    "appId": "your.id",
    "files": [
      "dist/**/*"
    ],
    "directories": {
      "output": "build"
    },
    "linux": {
      "category": "Video",
      "target": [
        "deb",
        "snap"
      ]
    }
  },
  "dependencies": {
    "@types/core-js": "^2.5.0",
    "moment": "^2.22.2",
    "winston": "^3.0.0"
  }
}

Here's my main.ts:

import { app, BrowserWindow, ipcMain, crashReporter } from "electron";
import * as path from "path";

process.env.ELECTRON_PROCESS_NAME = "main";

import { initLogger } from "../common/logging";

let log = initLogger();
log.info("=== Starting up ===");

let mainWindow: Electron.BrowserWindow = null;

function createMainWindow() {
    if (mainWindow === null) {
        mainWindow = new BrowserWindow({
            height: 600,
            width: 800,
        });
        mainWindow.loadFile(path.join(__dirname, "../../static/ui.html"));
        mainWindow.webContents.openDevTools();
    }
}

app.on("ready", createMainWindow);

app.on("window-all-closed", () => {
    // On macOS it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q
    if (process.platform !== "darwin") {
        log.info("Exiting...");
        app.quit();
    }
});

app.on("activate", () => {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    createMainWindow();
});
siride
  • 165,323
  • 4
  • 31
  • 54

2 Answers2

8

A simple solution is to disable devtools because they wont be in your final version anyway. You could add a "--debug" argument (parsed in main.js) and add it to the "start" script in package.json. Then, the devtools wont open in your test if you don't add the argument.

If you want to keep devtools or just be sure to exit your app, you can stop your main process with the Node.js exit() function which is a bit brutal:

afterEach(function () {
    if (this.app && this.app.isRunning()) {
        this.app.mainProcess.exit(0);
    }
});

I choose to have a mix of both disabling devtools and be sure to exit (to prevent any bug in my app that could prevent to exit). I'm not familiar with Mocha beceause I use AVA instead and I work in JS instead of TS. So I tried to adapt this code but there may be some mistakes:

beforeEach(function () {
    this.app = new Application({
        path: electronPath,
        // you could add --debug in the following array
        // don't do it, to keep the devtools closed
        args: [path.join(__dirname, '..')]
    } as any);
    return this.app.start();
})

// use ES8 'async' to be able to wait
afterEach(async function () {
    if (this.app && this.app.isRunning()) {
        // get the main process PID
        let pid = this.app.mainProcess.pid;

        // close the renderer window using its own js context
        // to get closer to the user action of closing the app
        // you could also use .stop() here
        await this.app.client.execute(() => {
            window.close();
        });

        // here, the app should be close

        try {
            // check if PID is running using '0' signal (throw error if not)
            process.kill(pid, 0);
        }
        catch(e) {
            // error catched : the process was not running

            // do someting to end the test with success !
            return;
        }

        // no error, process is still running, stop it
        this.app.mainProcess.exit(1);
        // do someting to end the test with error
        return
    }
});
Anozer
  • 411
  • 1
  • 4
  • 11
  • I will see if this will solve my problem. At the very least, I think your suggestion to have dev tools disabled when running tests (via environment variable or some such) is probably the way to go. – siride Aug 31 '18 at 15:36
0

Try this

  app.on('window-all-closed', () => {
    // prevent quit on MacOS. But also quit if we are in test.
    if (process.platform !== 'darwin' || isTest) {
      app.quit();
    }
  });

And isTest is const isTest = process.env.NODE_ENV === 'test';

Then the app will properly quit!

To get process.env.NODE_ENV, you may need to update your webpack to use DefinePlugin:

  new webpack.DefinePlugin({
    'process.env.NODE_ENV': `"${process.env.NODE_ENV ?? 'production'}"`,
    // global: {},
  }),
林东吴
  • 73
  • 5