Cypress: Use config to define absolute paths

Created on 31 Jan 2019  路  10Comments  路  Source: cypress-io/cypress

Current behavior:

I want to use absolute paths for module imports. Thus I set cypress/tsconfig.json like below

{
  "compilerOptions": {
    "allowJs": true,
    "baseUrl": ".",
    "paths": {
      "@api/*": [
        "support/api/*"
      ]
    },
    "types": [
      "cypress"
    ]
  },
  "include": [
    "**/*.*"
  ]
}

And in my test code, the profile_api is in cypress/support/api/profile_api, so the first import line:

import { getProfiles } from '@api/profile_api' 

But always got the error:

Error: Cannot find module '@api/profile_api' from ...

Desired behavior:

Should be able to use absolute paths from tsconfig.json

Versions

[email protected]

Most helpful comment

I found cypress-webpack-preprocessor can fullfill it

In plugin file:

     const webpack = require('@cypress/webpack-preprocessor')
     module.exports = (on) => {
         const options = {
             // send in the options from your webpack.config.js, so it works the same
             // as your app's code
             webpackOptions: require('../../webpack.config'),
             watchOptions: {},
         }
       on('file:preprocessor', webpack(options))
     }

in webpack.config.js:

var path = require('path')

     module.exports = {
         resolve: {
             alias: {
                 Api: path.resolve(__dirname, 'cypress/support/api'),
                 Helper: path.resolve(__dirname, 'cypress/support/helper'),
                 Obj: path.resolve(__dirname, 'cypress/support/obj'),
                 Fixtures: path.resolve(__dirname, 'cypress/fixtures'),
                 Plugins: path.resolve(__dirname, 'cypress/plugins')
             }
         }
     }

Now, instead of using relative paths when importing like so:

    import Utility from '../../api/utility';

I can use the alias:

    import Utility from 'Api/utility';

All 10 comments

I found cypress-webpack-preprocessor can fullfill it

In plugin file:

     const webpack = require('@cypress/webpack-preprocessor')
     module.exports = (on) => {
         const options = {
             // send in the options from your webpack.config.js, so it works the same
             // as your app's code
             webpackOptions: require('../../webpack.config'),
             watchOptions: {},
         }
       on('file:preprocessor', webpack(options))
     }

in webpack.config.js:

var path = require('path')

     module.exports = {
         resolve: {
             alias: {
                 Api: path.resolve(__dirname, 'cypress/support/api'),
                 Helper: path.resolve(__dirname, 'cypress/support/helper'),
                 Obj: path.resolve(__dirname, 'cypress/support/obj'),
                 Fixtures: path.resolve(__dirname, 'cypress/fixtures'),
                 Plugins: path.resolve(__dirname, 'cypress/plugins')
             }
         }
     }

Now, instead of using relative paths when importing like so:

    import Utility from '../../api/utility';

I can use the alias:

    import Utility from 'Api/utility';

Did you experience Cypress freezing up at all when running a spec file with an absolute import? If so, how did you get around this?

@ennisbenjamind No, everything looks good in this solution.

you have any other plugins set up by chance?

Alternatively, to references files relative to cypress dir like so:

import * as h from '~/support/helpers.js';  // -> cypress/support/helpers.js

Do:

yarn add -D @cypress/webpack-preprocessor

cypress/plugins/index.js:

const path = require('path');

const webpack = require('@cypress/webpack-preprocessor');

class CypressFileKindPlugin {
    constructor(source, target) {
        this.source = source;
        this.target = target;
    }

    apply(resolver) {
        resolver.plugin(this.source, (request, callback) => {
            if (request.request.startsWith('~/')) {
                const newPath = path.join(__dirname, '..', request.request.substr(2));
                const newRelativePath = path.join(
                    request.relativePath,
                    path.relative(request.path, newPath));
                const obj = Object.assign({}, request, {
                    path: newPath,
                    relativePath: newRelativePath,
                    request: undefined,
                });
                resolver.doResolve(this.target, obj, null, callback);
            } else
                return callback();
        });
    }
}

module.exports = (on, config) => {
    on('file:preprocessor', webpack({
        webpackOptions: {
            resolve: {
                plugins: [
                    new CypressFileKindPlugin('described-resolve', 'raw-file'),
                ],
            }
        },
    }));
}

If you want to add babel plugin, take a look at the default one, then:

module.exports = (on, config) => {
    const webpackOptions = Object.assign({}, webpack.defaultOptions.webpackOptions, {
        module: {
            rules: [{
                test: /\.js$/,
                exclude: [/node_modules/],
                use: [{
                    loader: 'babel-loader',
                    options: {
                        // babelrc: false,  // in case you have .babelrc
                            // having .babelrc and passing options inline will result
                            // in a merge, potentially running plugins more than once
                        presets: ['env'],
                        plugins: ['transform-object-rest-spread'],
                    },
                }],
            }],
        },
        ...
    });
    on('file:preprocessor', webpack({webpackOptions}));
}

For those of you using Create React App with react-scripts webpack config, the following worked for me. I referenced the solution above and https://github.com/cypress-io/cypress-webpack-preprocessor/tree/master/examples/react-app.

module.exports = on => {
  // find the Webpack config used by react-scripts
  const webpackOptions = findWebpack.getWebpackOptions();

  if (!webpackOptions) {
    throw new Error('Could not find Webpack in this project 馃槩');
  }

  const cleanOptions = {
    reactScripts: true
  };

  findWebpack.cleanForCypress(cleanOptions, webpackOptions);

  const options = {
    webpackOptions,
    watchOptions: {}
  };

  // Define your alias(es) here:
  options.webpackOptions.resolve.alias.cypress = path.resolve(process.cwd(), 'cypress');

  on('file:preprocessor', webpackPreprocessor(options));
}

Cypress now uses ts-node instead of webpack, so I tried another method at solving this.

ts-node does not support the paths option https://github.com/TypeStrong/ts-node/issues/138, so my workaround is to use the tsconfig-paths package. It has the advantage of reusing the paths from your top-level tsconfig, so you don't have to duplicate them in the cypress tsconfig.json

Version

Cypress 5.0

Setup

tsconfig.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
        // Add your paths here
    }
  }
}

cypress/tsconfig.json

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "baseUrl": "..",
  }
}

cypress/plugins/index.ts

// Make sure this is first
import "./register-tsconfig-paths"

export default function (on, config) {
  return config
}

cypress/plugins/register-tsconfig-paths

import tsConfig from "../../tsconfig.json"; // Your top-level config!
import * as tsConfigPaths from  "tsconfig-paths";

const baseUrl = "./";
tsConfigPaths.register({
  baseUrl,
  paths: tsConfig.compilerOptions.paths
});

I just started using this workaround so I'm not sure if its bullet-proof, hopefully this saves someone an afternoon of head scratching.

EDIT: Two months later, we haven't had any issues. For our solution we need to pass custom options to tsconfig-paths. If you don't need to, use the more simpler solution @flybayer posted below

@marklawlor oh hallelujah!! Finally found your comment after trying several outdated blog post.

This was all I needed to make it work:

require("tsconfig-paths").register()

"e2e": "NODE_ENV=src cypress open" worked for me

"e2e": "NODE_ENV=src cypress open" worked for me

I would recommend against using this solution. Using a nonstandard NODE_ENV could have unintended consequences (eg NextJS will error with a NONSTANDARD_NODE_ENV_ERROR, webpack will bundle differently, etc). This also prevents you from easily using NODE_ENV flags in your own codebase.

Was this page helpful?
0 / 5 - 0 ratings