vue-cli-plugin-electron-builder icon indicating copy to clipboard operation
vue-cli-plugin-electron-builder copied to clipboard

Windows: testWithSpectron didn't kill the electron:serve process

Open baruchiro opened this issue 4 years ago • 10 comments

Describe the bug We are testing our Electron with { testWithSpectron } from 'vue-cli-plugin-electron-builder'. The stopServe and done are called, Jest says the test ends, but

Jest did not exit one second after the test run has completed. This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with --detectOpenHandles to troubleshoot this issue.

I can see the serve process in the TaskManager, and it not ended:

Screenshots

image image

Environment (please complete the following information):

  • OS and version: image
  • node version: v12.13.1
  • npm version: 6.13.4
  • yarn version (if used): 1.21.1
  • vue-cli-plugin-electron-builder version : 1.4.4
  • electron version: ^5.0.0
  • other vue plugins used:
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-eslint": "^4.1.0",
"@vue/cli-plugin-router": "^4.1.0",
"@vue/cli-plugin-unit-jest": "^4.1.2",
"@vue/cli-plugin-vuex": "^4.1.0",
"vue-cli-plugin-element": "^1.0.1"
  • custom config for vcp-electron-builder:
  • (if possible) link to your repo: https://github.com/baruchiro/israeli-bank-scrapers-desktop/tree/UpdateVersions (Note to the branch)

Additional context Command: jest test/e2e --config='test/e2e/jest.e2e.config.js' Config:

module.exports = {
  preset: '@vue/cli-plugin-unit-jest',
  testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
};

Test:

import { testWithSpectron } from 'vue-cli-plugin-electron-builder';

jest.setTimeout(100000);

describe('Launch', () => {
  let app;
  let stopServe;
  let win;
  let client;

  beforeEach(async () => {
    // Wait for dev server to start
    ({ app, stopServe } = await testWithSpectron());
    win = app.browserWindow;
    ({ client } = app);
  });

  test('shows the proper application title', async () => {
    // Window was created
    expect(await client.getWindowCount()).toBe(1);
    // It is not minimized
    expect(win.isMinimized()).toBe(false);
    // Window is visible
    expect(win.isVisible()).toBe(true);
    // Size is correct
    const { width, height } = win.getBounds();
    expect(width).toBeGreaterThan(0);
    expect(height).toBeGreaterThan(0);
    // App is loaded properly
    expect(
      /israeli-bank-scrapers-desktop/.test(
        await client.getHTML('#app'),
      ),
    ).toBe(true);
  });

  afterEach(async (done) => {
    await stopServe();
    done();
    console.log('done');
  });
});

baruchiro avatar Jan 16 '20 09:01 baruchiro

one more strange thing that I've found is that if there is more than a one test file the test will be resolved successfully.

but for some reason, this trick won't work on Github Workflow

Arielgordon123 avatar Jan 16 '20 11:01 Arielgordon123

one more strange thing that I've found is that if there is more than a one test file the test will be resolved successfully.

but for some reason, this trick won't work on Github Workflow

See this action as example.

baruchiro avatar Jan 16 '20 11:01 baruchiro

Try upgrading to the v2.0 beta. It uses a newer version of execa (used to spawn the dev server), and may fix your problem.

nklayman avatar Jan 18 '20 18:01 nklayman

Try upgrading to the v2.0 beta. It uses a newer version of execa (used to spawn the dev server), and may fix your problem.

I've tried with v2.0 beta but still having the same issue however the open handle is different this time. With v1.4.4 the open handle was PIPEWRAP but now its PROCESSWRAP. Not sure if that is significant.

image

deankinane avatar Jan 18 '20 23:01 deankinane

What happens if you run the testWithSpectron function in a node script (without jest)? Does it exit properly? Also, try waiting 6 seconds after calling stopServe, as execa should force kill the process after 5 seconds if it doesn't exit initially (https://github.com/sindresorhus/execa#killsignal-options).

nklayman avatar Jan 21 '20 21:01 nklayman

This issue has been automatically closed because there has been no response to our request for more information from the original author. With only the information that is currently in the issue, we don't have enough information to take action. Please reach out if you have or find the answers we need so that we can investigate further.

no-response[bot] avatar Jan 30 '20 20:01 no-response[bot]

OK, it reproduced with ^2.0.0-rc.6, see the branch test.

I have a node script and Jest. In node script, it works as expected, but with Jest, Github Actions, and Windows, it still gives me the "test didn't finish" message, and after this message it is waiting forever.

It is not reproduced locally.

CI: https://github.com/brafdlog/budget-tracking/runs/2038188083?check_suite_focus=true

Also, note. When I'm running it locally in Windows, in Jest there are "Electron Terminal" windows (you know, when you open a CLI process in a new window) that opened and closed immediately during the test.

package.json

https://github.com/brafdlog/budget-tracking/blob/341cb6ed8ecaf999187067ea3f0e1eba13a6147f/package.json

{
  "name": "hiuvi",
  "version": "0.1.3-beta",
  "author": "Jonathan Goldfarb <[email protected]>",
  "description": "An electron app for automating expense tracking",
  "license": "MIT",
  "main": "background.js",
  "private": true,
  "repository": "https://github.com/brafdlog/budget-tracking",
  "homepage": "https://github.com/brafdlog/budget-tracking#README.md",
  "scripts": {
    "serve": "vue-cli-service electron:serve",
    "serve:docker": "yarn serve --no-sandbox",
    "build": "vue-cli-service electron:build",
    "postinstall": "electron-builder install-app-deps",
    "postuninstall": "electron-builder install-app-deps",
    "prepare": "node scripts/copyCategoryCalculationScript.js",
    "lint": "eslint --ext .js,.vue,.ts -f ./node_modules/eslint-friendly-formatter src test",
    "lint:fix": "yarn lint --fix",
    "typeCheck": "tsc --noEmit",
    "unit": "vue-cli-service test:unit",
    "e2e": "vue-cli-service test:unit --config='test/e2e/jest.e2e.config.js'",
    "test": "yarn unit && yarn e2e"
  },
  "husky": {
    "hooks": {
      "pre-push": "yarn lint && yarn typeCheck"
    }
  },
  "dependencies": {
    "@getstation/electron-google-oauth2": "2.1.0",
    "@sentry/electron": "^1.3.0",
    "@vue/composition-api": "^1.0.0-beta.14",
    "analytics-node": "^3.4.0-beta.3",
    "core-js": "^3.4.4",
    "csv-parse": "^4.14.1",
    "csv-stringify": "^5.5.3",
    "direct-vuex": "^0.12.0",
    "electron-devtools-installer": "^3.1.1",
    "electron-log": "^4.1.1",
    "emittery": "^0.7.1",
    "googleapis": "^59.0.0",
    "israeli-bank-scrapers-core": "^1.0.1",
    "keytar": "^5.2.0",
    "lodash": "^4.17.15",
    "moment": "^2.24.0",
    "node-machine-id": "^1.1.12",
    "puppeteer-core": "^5.5.0",
    "vue": "^2.6.10",
    "vue-router": "^3.1.6",
    "vuetify": "^2.2.22",
    "vuex": "^3.1.3",
    "vuex-persist": "^2.2.0",
    "ynab": "^1.19.0"
  },
  "devDependencies": {
    "@babel/core": "^7.9.0",
    "@babel/plugin-proposal-class-properties": "^7.10.4",
    "@babel/preset-env": "^7.9.5",
    "@babel/preset-typescript": "^7.10.1",
    "@babel/register": "^7.9.0",
    "@types/analytics-node": "^3.1.4",
    "@types/jest": "^26.0.10",
    "@types/lodash": "^4.14.158",
    "@types/node": "12",
    "@types/puppeteer-core": "^5.4.0",
    "@types/webdriverio": "^4.0.0",
    "@typescript-eslint/eslint-plugin": "^3.7.0",
    "@typescript-eslint/parser": "^3.2.0",
    "@vue/cli-plugin-babel": "^4.3.1",
    "@vue/cli-plugin-eslint": "^4.3.1",
    "@vue/cli-plugin-router": "^4.3.1",
    "@vue/cli-plugin-typescript": "^4.4.4",
    "@vue/cli-plugin-unit-jest": "^4.5.4",
    "@vue/cli-plugin-vuex": "^4.3.1",
    "@vue/cli-service": "^4.3.1",
    "@vue/eslint-config-prettier": "^6.0.0",
    "@vue/eslint-config-typescript": "^5.0.2",
    "@vue/test-utils": "^1.0.3",
    "babel-core": "^7.0.0-bridge.0",
    "babel-eslint": "^10.1.0",
    "babel-jest": "^26.3.0",
    "babel-plugin-component": "^1.1.1",
    "electron": "^9.0.0",
    "eslint": "^7.1.0",
    "eslint-config-airbnb-base": "^14.1.0",
    "eslint-config-prettier": "^6.11.0",
    "eslint-friendly-formatter": "^4.0.1",
    "eslint-import-resolver-alias": "^1.1.2",
    "eslint-loader": "^4.0.2",
    "eslint-plugin-html": "^6.0.2",
    "eslint-plugin-import": "^2.20.2",
    "eslint-plugin-jest": "^23.20.0",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-prettier": "^3.1.3",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.1",
    "eslint-plugin-vue": "^6.2.2",
    "husky": "^4.2.5",
    "identity-obj-proxy": "^3.0.0",
    "jest": "^26.4.2",
    "jest-circus": "^26.4.2",
    "jest-config": "^26.4.2",
    "jest-each": "^26.4.2",
    "jest-environment-node": "^26.6.2",
    "ncp": "^2.0.0",
    "node-loader": "^1.0.2",
    "prettier": "^2.0.5",
    "replace-in-file": "5.0.2",
    "sass": "^1.26.3",
    "sass-loader": "^8.0.0",
    "spectron": "^11.0.0",
    "ts-jest": "^26.3.0",
    "typescript": "^3.9.7",
    "vue-cli-plugin-electron-builder": "^2.0.0-rc.6",
    "vue-cli-plugin-vuetify": "~2.0.5",
    "vue-jest": "4.0.0-beta.5",
    "vue-template-compiler": "^2.6.11",
    "vuetify-loader": "^1.4.4"
  }
}
script.js

https://github.com/brafdlog/budget-tracking/blob/341cb6ed8ecaf999187067ea3f0e1eba13a6147f/script.js

const { testWithSpectron } = require('vue-cli-plugin-electron-builder');
const spectron = require('spectron');

(async function start() {
    console.log('serve')
    console.time('serve')
    const { app, stopServe, stdout } = await testWithSpectron(spectron)
    console.timeEnd('serve')
    console.log('served')
    console.log(stdout)
    
    const { client } = app;

    console.log('waitUntilWindowLoaded')
    await client.waitUntilWindowLoaded();
    console.log('finish waitUntilWindowLoaded')

    console.log(await client.getWindowCount())

    const appElement = await client.$('#app');
    console.log(/Hiuvi/.test(await appElement.getHTML()))

    console.log('stopServe')
    await stopServe()
    console.log('finish stopServe')
})()
launch.spec.ts https://github.com/brafdlog/budget-tracking/blob/341cb6ed8ecaf999187067ea3f0e1eba13a6147f/test/e2e/specs/launch.spec.ts
import fs from 'fs';
import path from 'path';
import { testWithSpectron } from 'vue-cli-plugin-electron-builder';
import spectron from 'spectron';
import {
  Application, SpectronClient, SpectronWindow, StopServe
} from '../type';
// import Interactions from '../utils/interactions';

const screenshotsDir = './screenshots';

jest.setTimeout(1000000);

describe('Launch', () => {
  let app: Application;
  let stopServe: StopServe;
  let browserWindow: SpectronWindow;
  let client: SpectronClient;
  // let interactions: Interactions;

  beforeAll(async () => {
    let stdout: string;
    ({ app, stopServe, stdout } = await testWithSpectron(spectron));

    // eslint-disable-next-line no-console
    console.log(stdout);
  });

  beforeEach(async () => {
    app = await app.restart();

    ({ client, browserWindow } = app);
    await client.waitUntilWindowLoaded();
    // interactions = new Interactions(client);
  });

  test('shows the proper application title', async () => {
    // Window was created
    expect(await client.getWindowCount()).toBe(1);
    // It is not minimized
    expect(await browserWindow.isMinimized()).toBe(false);
    // Window is visible
    expect(await browserWindow.isVisible()).toBe(true);
    // Size is correct
    const { width, height } = await browserWindow.getBounds();
    expect(width).toBeGreaterThan(0);
    expect(height).toBeGreaterThan(0);
    // App is loaded properly
    const appElement = await client.$('#app');
    expect(await appElement.getHTML()).toMatch('Hiuvi');
  });

  // test.skip('Hide AddScraper components by default', async () => {
  //   const addScrapers = await interactions.getAddScrapers();
  //   // @ts-expect-error
  //   const visiblities = await Promise.all(addScrapers.map((scraper) => scraper.isVisible()));
  //   expect(visiblities).not.toContain(true);
  //   expect(visiblities).toContain(false);
  // });

  // test.skip('Show AddScraper components when clicking on AddScraper', async () => {
  //   await interactions.toggleLeftDrawer();
  //   await interactions.clickCollapseAddImporter();

  //   const addScrapers = await interactions.getAddScrapers();
  //   // @ts-expect-error
  //   const visiblities = await Promise.all(addScrapers.map((scraper) => scraper.isVisible()));
  //   expect(visiblities).not.toContain(false);
  // });

  afterEach(async () => {
    if (global.lastTest.failed) {
      if (!fs.existsSync(screenshotsDir)) {
        fs.mkdirSync(screenshotsDir);
      }

      const screenshotFile = path.join(screenshotsDir, `${global.lastTest.test.name.replace(/\s/g, '')}.png`);
      const imgBuffer = await browserWindow.capturePage();
      fs.writeFileSync(screenshotFile, imgBuffer.toBitmap());
    }
  });

  afterAll(async () => stopServe());
});

declare global {
  namespace NodeJS {
    interface Global {
      lastTest: any;
    }
  }
}

Update:

I tried to wait for 10 seconds after the stopServe.

https://github.com/brafdlog/budget-tracking/blob/7d49a2874f6d8dafa09fa2e4dc8b18f0a05fa254/test/e2e/specs/launch.spec.ts#L85-L90

image

Note that the CI is still running. It means that Jest reported about the "did not exit", and still waiting.

baruchiro avatar Mar 05 '21 08:03 baruchiro

Seems like it may be related to this: https://github.com/sindresorhus/execa/issues/96. I'm not sure what else it could be, as stopServe kills it pretty clearly. I'll see if I can find a way to make sure it kills child processes as well.

nklayman avatar Mar 05 '21 23:03 nklayman

Okay I changed the command to use SIGKILL to kill the dev server, which might fix it. Can you update your app to use the git repo as the version for this plugin and then let me know if it works now?

nklayman avatar Mar 12 '21 20:03 nklayman

Still not working.

https://github.com/brafdlog/budget-tracking/pull/203/checks?check_run_id=2115697081

Do I need to wait 5 seconds?

baruchiro avatar Mar 15 '21 19:03 baruchiro