argos-ci / jest-puppeteer

Run tests using Jest & Puppeteer 🎪✨

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[jest-dev-server] not working server.usedPortAction: 'ignore' config when running React application with react-scripts

tmilar opened this issue · comments

🐛 Bug Report

I have configured jest-puppeteer to start up my React application (which runs using react-scripts / CRA) using the server config, together with usedPortAction: 'ignore'.

When running Jest, if the React application was not already running, jest-dev-server starts it up and then my tests are run correctly.

But, if my React app was already running, jest-dev-server fails to detect (and ignore) it, tries to run it again, and ends up failing with the following error; and my tests are not run.

[jest-dev-server] Something is already running on port 3000.

To Reproduce

jest-puppeteer.config.js file:

module.exports = {
  // ...
  server: {
    command: 'npm start',
    port: 3000,
    usedPortAction: 'ignore',
  },
}

jest.config.js

module.exports = {
  // presets are a set of options that are applied to all tests
  preset: 'jest-puppeteer',
}

package.json:

"scripts": {
  "start": "craco start",
  "test": "jest --findRelatedTests test/integration/*.test.tsx"
}

Steps:

1- Start CRA app with npm start, wait for app to be ready (listening on port 3000).
2- Run tests with npm test

Expected behavior

As I'm using the config usedPortAction: 'ignore' config, when my app is already running on the specified port (3000), jest-dev-server should detect it that it's already running and don't try to start it up; then proceed to run my tests.

System:

  • OS: macOS 12.4
  • CPU: (10) arm64 Apple M1 Pro
  • Memory: 107.61 MB / 16.00 GB
  • Shell: 5.8.1 - /bin/zsh

Binaries:

  • Node: 16.14.2 - ~/.nvm/versions/node/v16.14.2/bin/node
  • Yarn: 1.22.18 - ~/.nvm/versions/node/v16.14.2/bin/yarn
  • npm: 8.5.0 - ~/.nvm/versions/node/v16.14.2/bin/npm
  • Watchman: 2023.02.27.00 - /opt/homebrew/bin/watchman

npmPackages:

  • @craco/craco: ^7.0.0 => 7.1.0
  • jest-puppeteer: ^8.0.6 => 8.0.6
  • react-scripts: ^5.0.1 => 5.0.1

Issue Analysis

I've been reviewing the code responsible for detecting if the port is being used:

const checkIsPortBusy = async (config: Config): Promise<boolean> => {
return new Promise<boolean>((resolve) => {
const server = createServer()
.once("error", (err: NodeJS.ErrnoException) => {
if (err.code === "EADDRINUSE") {
resolve(true);
} else {
resolve(false);
}
})
.once("listening", () => {
server.once("close", () => resolve(false)).close();
})
.listen(config.port);
});
};

The problem with this code, is that the host config property is not being specified. When this happens, as of Node v16, per the Node docs:

If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise.

Since in my environment seems to occur that IPv6 is available, the default host used is ::, so when calling .listen(config.port) (without specifying the host in a next parameter), this server ends up listening (instead of resulting in EADDRINUSE), and on http://:::3000 address.

I have been able to validate this by editing the above snippet and running it on my machine:

const checkIsPortBusy = async (config)=>{
    return new Promise((resolve)=>{
        const server = node_net.createServer().once("error", (err)=>{
            if (err.code === "EADDRINUSE") {
                resolve(true);
            } else {
                resolve(false);
            }
        }).once("listening", ()=>{
           console.log('Server listening:', `http://${server.address().address}:${server.address().port}`);
            server.once("close", ()=>resolve(false)).close();
        }).listen(config.port);
    });
};

checkIsPortBusy({ port: 3000 })
  .then(result => console.log('port busy?', result))
  .catch(e => console.error('err', e))

When my React application is up in port 3000, running this snippet with node, results in the output:

Server listening: http://:::3000
port busy? false

On the other hand, react-scripts start script, runs the React application server by default on host '0.0.0.0', therefore ends up listening in 'http://0.0.0.0:3000' address.

Proposal

I think the easiest way to solve this, is to allow specifying the host as a config option for the Node server used to check if the port is busy, just like we already do for the port property.

This way, we could specify the exact host: '0.0.0.0', and checkIsPortBusy() would work fine for this case.

The following code is working properly

const checkIsPortBusy = async (config)=>{
    return new Promise((resolve)=>{
        const server = node_net.createServer().once("error", (err)=>{
            if (err.code === "EADDRINUSE") {
                resolve(true);
            } else {
                resolve(false);
            }
        }).once("listening", ()=>{
           console.log('Server listening:', `http://${server.address().address}:${server.address().port}`);
            server.once("close", ()=>resolve(false)).close();
        }).listen(config.port, config.host);
    });
};

checkIsPortBusy({ port: 3000, host: '0.0.0.0' })
  .then(result => console.log('port busy?', result))
  .catch(e => console.error('err', e))

Output:

port busy? true

Also, with this change in my node_modules folder, when I update my jest-puppeteer.config.js file like so:

module.exports = {
  // ...
  server: {
    command: 'npm start',
    port: 3000,
    host: '0.0.0.0',
    usedPortAction: 'ignore',
  },
}

Everything works smoothly, my test runs and doesn't try to spin up my React app again. Running npm test outputs:

port busy? true

Port 3000 is already used. Assuming server is already running.