Skip to content

Interrupted file:preprocessor child process with disconnected IPC channel during preprocessor:close causes unclear plugin error #21010

@tomudding

Description

@tomudding

Current behavior

When interrupting the Cypress process with SIGINT during the execution of a test the following error is displayed:

We stopped running your tests because a plugin crashed.

Your pluginsFile threw an error from: /home/tom/cypress-test/src/plugin.ts

Error [ERR_IPC_CHANNEL_CLOSED]: Channel closed
    at new NodeError (node:internal/errors:370:5)
    at ChildProcess.target.send (node:internal/child_process:724:16)
    at Object.send (/home/tom/.cache/Cypress/9.5.3/Cypress/resources/app/packages/server/lib/plugins/util.js:32:25)
    at EventEmitter.<anonymous> (/home/tom/.cache/Cypress/9.5.3/Cypress/resources/app/packages/server/lib/plugins/preprocessor.js:45:16)
    at EventEmitter.emit (node:events:406:35)
    at Object.removeFile (/home/tom/.cache/Cypress/9.5.3/Cypress/resources/app/packages/server/lib/plugins/preprocessor.js:119:17)
    at Object.options.onBrowserClose (/home/tom/.cache/Cypress/9.5.3/Cypress/resources/app/packages/server/lib/open_project.js:135:40)
    at EventEmitter.<anonymous> (/home/tom/.cache/Cypress/9.5.3/Cypress/resources/app/packages/server/lib/browsers/index.js:212:19)
    at Object.onceWrapper (node:events:513:28)
    at EventEmitter.emit (node:events:394:28)
    at BrowserWindow.<anonymous> (/home/tom/.cache/Cypress/9.5.3/Cypress/resources/app/packages/server/lib/browsers/electron.js:431:25)
    at Object.onceWrapper (node:events:514:26)
    at BrowserWindow.emit (node:events:406:35)

Based on the text above the stack trace I would assume I made a mistake in my plugin.ts pluginsFile, however, that is actually empty. Without a custom pluginFile, the error will default to the 'standard' Cypress plugin file (plugin.js).

Desired behavior

I would expect that Cypress checks whether or not the file:preprocessor child process is still connected or not terminated before trying to send something on its IPC channel. If disconnected or terminated, Cypress should not try to send something to the child process.

In other words, I would expect that I do not get an error.

Test code to reproduce

package.json:

{
  "name": "cypress-interrupt-test",
  "version": "1.0.0",
  "main": "index.ts",
  "scripts": {
    "start": "node --loader ts-node/esm index.ts"
  },
  "devDependencies": {
    "cypress": "^9.5.3",
    "ts-node": "^10.7.0",
    "typescript": "^4.6.3"
  }
}

index.ts:

import cypress from "cypress";

cypress.run({
    config: {
        baseUrl: "http://localhost:2000",
        supportFile: "src/overwrite.ts",
        pluginsFile: "src/plugin.ts",
        integrationFolder: "cypress/integration",
        testFiles: ["**/*.*"],
    },
    spec: null,
    quiet: true,
});

src/overwrite.ts:

// is empty

src/plugin.ts:

/**
 * @type {Cypress.PluginConfig}
 */
module.exports = (on, config) => {}

cypress/integration/interrupt.spec.ts:

describe('an elaborate test', () => {
    it('can wait', () => {
        cy.wait(10000); // wait to allow us to interrupt the Cypress process
    });
});

The SUT is just a simple web server with two pages, / containing a link to /page2.html

Execute npm start or node --loader ts-node/esm index.ts.

Cypress Version

9.5.3

Other

Tested on Ubuntu 20.04 and 21.10, with node versions v16.5.0, v17.4.0, and v17.9.0. Also tested with Cypress 9.5.0.

The following check is performed before trying to send something, however, it purely checks whether or not the process is killed. The process might already have been disconnected at this point (or even terminated).

if (aProcess.killed) {
return
}

aProcess looks like this when it passes the if above:

{
    ...,
    connected: false,
    signalCode: 'SIGINT',
    exitCode: null,
    killed: false,
    ...,
}

To at least make sure that the process is not disconnected this additional check should be added:

diff --git a/packages/server/lib/plugins/util.js b/packages/server/lib/plugins/util.js
index 8d0e823847..b0b6e19076 100644
--- a/packages/server/lib/plugins/util.js
+++ b/packages/server/lib/plugins/util.js
@@ -25,7 +25,7 @@ module.exports = {
 
     return {
       send (event, ...args) {
-        if (aProcess.killed) {
+        if (aProcess.killed || !aProcess.connected) {
           return
         }

You could go even further by checking the termination status through .exitCode, but I do not think that would be more helpful in this situation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions