ocavue / jest-puppeteer-istanbul

Collect code coverage information from end-to-end jest puppeteer tests
MIT License
27 stars 1 forks source link

Code coverage is not collected #42

Closed IvanSavoskin closed 3 years ago

IvanSavoskin commented 3 years ago

After completing the tests, the puppeteer coverage is not collected. A file with the entry {} is created.

tsconfig.json

{
    "compilerOptions": {
        "allowJs": false,
        "target": "es6",
        "module": "es6",
        "rootDirs": [
            "./src",
            "./test"
        ],
        "lib": [
            "es5",
            "es6",
            "dom"
        ],
        "jsx": "react",
        "sourceMap": true,
        "strict": true,
        "noStrictGenericChecks": false,
        "strictNullChecks": true,
        "noImplicitReturns": true,
        "moduleResolution": "node",
        "outDir": "./dist/",
        "resolveJsonModule": true,
        "esModuleInterop": true,
        "typeRoots": [
            "./custom_typings",
            "./node_modules/@types"
        ]
    },
    "exclude": [
        "./node_modules"
    ],
    "include": [
        "./src/**/*",
        "./custom_typings/**/*",
        "./test/**/*"
    ]
}

jest.config.js

module.exports = {
    preset: "./test/configs/testPreset",
    testMatch: ["**/?(*.)+(spec|test).[t]s"],
    testPathIgnorePatterns: ["/node_modules/", "dist"],
    moduleFileExtensions: ["js", "ts", "tsx"],
    setupFilesAfterEnv: [
        "jest-puppeteer-istanbul/lib/setup",
        "jest-puppeteer-allure/src/registerAllureReporter"
    ],
    setupFiles: [
        "<rootDir>/test/__setups__/setupEnzyme.ts"
    ],
    coverageReporters: ["json", "text", "lcov"],
    reporters: ["default", "jest-puppeteer-istanbul/lib/reporter", "jest-puppeteer-allure"],
    verbose: true,
    collectCoverage: true,
    collectCoverageFrom: [
        "src/**/*.{ts,tsx}",
        "!**/*d.ts",
        "!**/*d.ts",
        "!**/*scss",
        "!**/node_modules/**"
    ],
    coverageDirectory: "dist/coverage",
    moduleNameMapper: {
        "\\.scss$": "identity-obj-proxy",
        "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)$": "<rootDir>/test/__mocks__/fileMock.ts",
    }
};

.babelrc.json

{
  "presets": [["@babel/preset-env", {}], ["@babel/preset-react", {}, "react"], ["@babel/preset-typescript", {}, "typescript"]],
  "plugins": [["@babel/plugin-proposal-class-properties", {}], ["@babel/plugin-proposal-optional-chaining", {}],
    ["@babel/plugin-proposal-nullish-coalescing-operator", {}], ["@babel/plugin-transform-runtime",
      {
        "regenerator": true
      }
    ]],
  "env": {
    "development": {
      "plugins": [
        ["istanbul", {}]
      ]
    }
  }
}

If you manually execute page.evaluate (() => window . coverage), then undefined is returned.

ocavue commented 3 years ago

@IvanSavoskin Thank you for posting this. Could you provide a reproduction repository for this issue?

This problem can also be related to a variety of causes, like which server are you using? how you start the server? which babel version are you using? what's your project structure. Because of that, debugging this issue without a reproduction repository is almost impossible.

IvanSavoskin commented 3 years ago

@ocavue Unfortunately, I do not have this opportunity, since the project is closed, I am ready to answer any questions. The project is built through webpack. Here are my configs: webpack.base.conf.js

const webpack = require("webpack")
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");

const PATHS = {
    src: path.join(__dirname, "../src"),
    dist: path.join(__dirname, "../dist"),
    global: path.resolve(__dirname, "../src/styles/globals.scss"),
    assets: "assets/"
};

module.exports = [
    {
        entry: {
            app: PATHS.src
        },
        externals: [
            {
                paths: PATHS
            }
        ],
        resolve: {
            extensions: [".ts", ".tsx", "json", ".css", ".scss", ".js", ".jsx", ".svg", ".ignore"]
        },
        module: {
            rules: [{
                test: /\.css$/,
                exclude: [/node_modules/, PATHS.global],
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            import: true,
                            sourceMap: true,
                            modules: {
                                mode: 'local',
                                localIdentName: '[path][name]__[local]--[hash]',
                                exportLocalsConvention: 'camelCase'
                            }
                        }
                    }, {
                        loader: "postcss-loader",
                        options: {
                            sourceMap: true,
                            postcssOptions: {
                                config: path.resolve(__dirname, "postcss.config.js")
                            }
                        }
                    }]
            }, {
                test: /\.scss$/,
                exclude: [/node_modules/, PATHS.global],
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            import: true,
                            sourceMap: true,
                            modules: {
                                mode: 'local',
                                localIdentName: '[path][name]__[local]--[hash]',
                                exportLocalsConvention: 'camelCase'
                            }
                        }
                    }, {
                        loader: "postcss-loader",
                        options: {
                            sourceMap: true,
                            postcssOptions: {
                                config: path.resolve(__dirname, "postcss.config.js")
                            }
                        }
                    }, {
                        loader: "sass-loader",
                        options: {
                            sourceMap: true
                        }
                    }]
            }, {
                test: /\.css$/,
                include: [PATHS.global],
                use: ["style-loader", "css-loader"]
            }, {
                test: /\.scss$/,
                include: [PATHS.global],
                use: ["style-loader", "css-loader", "sass-loader"]
            }, {
                test: /\.tsx?$/,
                use: [{
                    loader: "babel-loader"
                }]
            }, {
                test: /\.(ignore|zip|png|ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
                use: [{
                    loader: "file-loader",
                    options: {
                        name: "[name].[ext]",
                        outputPath: "static/assets/"
                    }
                }]
            }]
        },
        optimization: {
            splitChunks: {
                cacheGroups: {
                    vendor: {
                        name: "vendors",
                        test: /node_modules/,
                        chunks: "all",
                        enforce: true
                    }
                }
            }
        },
        output: {
            path: PATHS.dist,
            filename: `${PATHS.assets}js/[name]-bundle.[hash].js`,
            publicPath: process.env.NODE_ENV === "development" ? "/" : process.env.API_PREFIX + "/"
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: `${PATHS.src}/index.html`,
                filename: "./index.html"
            }),
            new CopyWebpackPlugin({
                patterns: [
                    {
                        from: `${PATHS.src}/static`,
                        to: `${PATHS.assets}/static`
                    }
                ]
            }),
            new CleanWebpackPlugin(),
            new webpack.ProgressPlugin(),
            new webpack.DefinePlugin({
                "process.env.DEV": JSON.stringify(process.env.NODE_ENV),
                "process.env.DEBUG": JSON.stringify(process.env.DEBUG),
                "process.env.API_PREFIX": JSON.stringify(process.env.API_PREFIX)
            })
        ]
    }
];

webpack.dev.conf.js

const webpack = require("webpack");
const { merge } = require("webpack-merge");
const [renderer] = require("./webpack.base.conf")

const buildWebpackConfig = [merge(renderer, {
    mode: "development",
    devtool: "eval-cheap-module-source-map",
    devServer: {
        contentBase: renderer.externals[0].paths.dist,
        port: 8081,
        overlay: {
            warnings: false,
            errors: true
        },
        hot: true,
        open: true,
        historyApiFallback: true
    },
    plugins: [
        new webpack.SourceMapDevToolPlugin({
            filename: "[file].map"
        })
    ]
})]

module.exports = new Promise(resolve => resolve(buildWebpackConfig));

webpack.build.conf.js

const {merge} = require("webpack-merge");
const [app] = require("./webpack.base.conf");

const buildWebpackConfig = [merge(app, {
    mode: "production"
})]

module.exports = new Promise(resolve => resolve(buildWebpackConfig));

I tried to run tests both by connecting to the dev assembly running via webpack-dev-server, and to the prod version running in open shift. The result was the same in both cases

ocavue commented 3 years ago

@IvanSavoskin Could you also provide the package.json after removing all sensitive information. My main interest are scrips, dependencies and devDependencies in the package.json.

IvanSavoskin commented 3 years ago

@ocavue Yes of course package.json

{
    "main": "src/index.tsx",
    "scripts": {
        "dev": "cross-env-shell NODE_ENV=development webpack-dev-server --config config/webpack.dev.conf.js",
        "build": "npm run type-css && cross-env-shell webpack --config config/webpack.build.conf.js",
        "type-css": "tcm -cs src && tsm --ignore \"**/globals.scss\" -n camel src",
        "type-css-watch": "tsm --ignore \"**/globals.scss\" -n camel src -w",
        "lint": "eslint --ext .ts,.tsx src --color",
        "test-local": "cross-env-shell TEST_NODE_ENV=local jest"
    },
    "author": "",
    "license": "ISC",
    "browsersList": [
        "> 1%",
        "last 3 version"
    ],
    "dependencies": {
        "@fortawesome/fontawesome-svg-core": "1.2.32",
        "@fortawesome/free-regular-svg-icons": "5.15.1",
        "@fortawesome/free-solid-svg-icons": "5.15.1",
        "@fortawesome/react-fontawesome": "0.1.12",
        "axios": "0.21.0",
        "fast-xml-parser": "3.17.4",
        "file-saver": "2.0.5",
        "font-awesome": "4.7.0",
        "get-port": "5.1.1",
        "http-status-codes": "2.1.4",
        "log4js": "6.3.0",
        "react": "17.0.1",
        "react-cookie": "4.0.3",
        "react-dom": "17.0.1",
        "react-redux": "7.2.2",
        "react-router-dom": "5.2.0",
        "react-svg": "11.1.0",
        "react-tabulator": "0.14.2",
        "redux-persist": "6.0.0",
        "redux-promise-middleware": "6.1.2",
        "redux-thunk": "2.3.0",
        "semantic-ui-react": "2.0.1",
        "webpack-node-externals": "2.5.2"
    },
    "devDependencies": {
        "@babel/core": "7.12.9",
        "@babel/plugin-proposal-class-properties": "7.12.1",
        "@babel/plugin-proposal-nullish-coalescing-operator": "7.12.1",
        "@babel/plugin-proposal-optional-chaining": "7.12.7",
        "@babel/plugin-transform-runtime": "7.12.1",
        "@babel/preset-env": "7.12.7",
        "@babel/preset-react": "7.12.7",
        "@babel/preset-typescript": "7.12.7",
        "@babel/runtime": "7.12.5",
        "@types/enzyme": "3.10.8",
        "@types/expect-puppeteer": "4.4.5",
        "@types/file-saver": "2.0.1",
        "@types/fs-extra": "9.0.1",
        "@types/jest": "26.0.16",
        "@types/jest-environment-puppeteer": "4.4.1",
        "@types/lodash": "4.14.165",
        "@types/puppeteer": "5.4.2",
        "@types/react": "17.0.0",
        "@types/react-dom": "17.0.0",
        "@types/react-redux": "7.1.11",
        "@types/react-router-dom": "5.1.6",
        "@types/react-test-renderer": "17.0.0",
        "@types/webpack": "4.41.25",
        "@typescript-eslint/eslint-plugin": "4.6.1",
        "@typescript-eslint/parser": "4.6.1",
        "@wojtekmaj/enzyme-adapter-react-17": "0.3.2",
        "autoprefixer": "10.0.1",
        "babel-loader": "8.1.0",
        "babel-plugin-istanbul": "6.0.0",
        "clean-webpack-plugin": "3.0.0",
        "copy-webpack-plugin": "6.2.1",
        "css-loader": "5.0.0",
        "css-mqpacker": "7.0.0",
        "cssnano": "4.1.10",
        "enzyme": "3.11.0",
        "eslint": "7.12.1",
        "eslint-config-airbnb-typescript": "12.0.0",
        "eslint-import-resolver-typescript": "2.3.0",
        "eslint-plugin-import": "2.22.1",
        "eslint-plugin-jsx-a11y": "6.4.1",
        "eslint-plugin-react": "7.21.5",
        "eslint-plugin-react-hooks": "4.2.0",
        "eslint-plugin-unicorn": "23.0.0",
        "expect-puppeteer": "4.4.0",
        "file-loader": "6.2.0",
        "fs-extra": "9.0.1",
        "html-webpack-plugin": "4.5.0",
        "identity-obj-proxy": "3.0.0",
        "jest": "26.6.3",
        "jest-puppeteer": "4.4.0",
        "jest-puppeteer-allure": "1.4.0",
        "jest-puppeteer-istanbul": "0.5.3",
        "node-sass": "5.0.0",
        "postcss-loader": "4.0.4",
        "puppeteer": "5.5.0",
        "react-test-renderer": "17.0.1",
        "sass-loader": "10.0.5",
        "semantic-ui-sass": "2.4.2",
        "style-loader": "2.0.0",
        "ts-enum-util": "4.0.2",
        "ts-jest": "26.4.4",
        "typed-css-modules": "0.6.4",
        "typed-scss-modules": "3.3.0",
        "typescript": "4.1.2",
        "webpack": "5.10.0",
        "webpack-cli": "3.3.12",
        "webpack-dev-server": "3.11.0",
        "webpack-merge": "5.4.0"
    }
}
ocavue commented 3 years ago

@IvanSavoskin I created a HelloWorld repository with your configuration above: https://github.com/ocavue/jest-puppeteer-istanbul-issue-42. However, it works pretty well and I still can't reproduce your issue. You can run the repository yourself by following the steps below.

$ git clone https://github.com/ocavue/jest-puppeteer-istanbul-issue-42
$ cd jest-puppeteer-istanbul-issue-42
$ yarn install 
$ yarn run dev

You should able to open http://localhost:8081 and window.__coverage__ should not be empty.

image

Then, in another terminal window, run yarn run jest and you should see the coverage information.

$ yarn run jest
yarn run v1.22.10
$ /Users/ocavue/code/play/jest-puppeteer-istanbul-issue-42/node_modules/.bin/jest
 PASS  test/e2e.spec.ts
  e2e test
    ✓ main (307 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.261 s
Ran all test suites.
----------------|---------|----------|---------|---------|-------------------
File            | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------|---------|----------|---------|---------|-------------------
All files       |   66.67 |       50 |      75 |   68.75 |
 HelloWorld.tsx |    62.5 |       50 |   66.67 |   64.29 | 13-17,25
 index.ts       |     100 |      100 |     100 |     100 |
 render.tsx     |     100 |      100 |     100 |     100 |
----------------|---------|----------|---------|---------|-------------------

One point worth noting is that I do update your jest.config.js in this repository since I don't know the content inside test/configs/testPreset.

// jest.config.js
module.exports = {
-   preset: "./test/configs/testPreset",
+   preset: "jest-puppeteer",

BTW, I didn't test jest-puppeteer-istanbul-issue-42 on windows and I don't know the behavior on windows.


As I mentioned earlier, a complex issue like this one (babel + webpack + jest + puppeteer) is very hard to debug without a minimal reproduction.

You may consider trimming your existing project by removing all sensitive code and data, or forking https://github.com/ocavue/jest-puppeteer-istanbul-issue-42 and adding something to it until it can't output the coverage. I'm happy to help, but I can't do much without a runnable example.

IvanSavoskin commented 3 years ago

@ocavue Thank you so much for your help, your answer made me think about it and I understood what the mistake was. In the afterEach method, I reset the page so that the results of previous tests do not affect the currently running one. By removing this in the beforeEach method, everything began to work as intended. There is one problem that now everything works correctly only when you run it through a script, and if you run a specific describe through IntelliJIdea, then the environment variables are not set and, accordingly, the jest-puppeteer-istanbul steps are simply skipped, but I'm not sure if this is critical and somehow need to edit

ocavue commented 3 years ago

In the afterEach method, I reset the page so that the results of previous tests do not affect the currently running one. By removing this in the beforeEach method, everything began to work as intended.

Glad to help you! Maybe I should export a function so that users can manually save the coverage result before reset pages. Something like jestPlaywright.saveCoverage(page) should be good enough.

There is one problem that now everything works correctly only when you run it through a script, and if you run a specific describe through IntelliJIdea, then the environment variables are not set and, accordingly, the jest-puppeteer-istanbul steps are simply skipped

Do you mean these two environment variables?

https://github.com/ocavue/jest-puppeteer-istanbul/blob/fd35368a1fec5feffd4db6a59187764df187afbd/src/reporter.ts#L37-L38

I don't use IntelliJ IDEA so I'm not sure how does IntelliJ IDEA runs a specific Jest describe. As far as I can guess, IntelliJ IDEA shouldn't skip jest-puppeteer-istanbul steps if IntelliJ IDEA

  1. can find the correct path of jest.config.js and understand its content, and
  2. respect collectCoverage: true in jest.config.js, and
  3. respect reporters: [..., "jest-puppeteer-istanbul/lib/reporter", ...] in jest.config.js.

I can look into it if I can get more information.

IvanSavoskin commented 3 years ago

@ocavue Yes, it might be convenient to be able to call such a function, but this is not at all critical, since it is easy to bypass

Yes, we are talking about these variables, if you look at the debug, then there is simply no entry into the constructor, and, accordingly, these variables are not set. I don’t know why this is happening, but it’s not critical

ocavue commented 3 years ago

respect reporters: [..., "jest-puppeteer-istanbul/lib/reporter", ...] in jest.config.js.

It seems that IDEA ignores jest-puppeteer-istanbul/lib/reporter in the jest.config.js because IDEA uses its own Jest reporter: image

Could you try to add --reporters jest-puppeteer-istanbul/lib/reporter in your IDEA Jest configuration like below. It seems IDEA works well after adding this option.

image

image

ocavue commented 3 years ago

@IvanSavoskin

If your IDEA works fine after adding the reporter options, I will add troubleshooting in the README in case someone else runs into this in the feature.

IvanSavoskin commented 3 years ago

@ocavue Yes, this is exactly the problem, just in parallel with you I discovered this problem and was going to write. It will be really cool if you add this to the readme for everyone else. Thanks for the wonderful tool, I have no more questions