Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use config to define absolute paths #3262

Closed
chuchu1313 opened this issue Jan 31, 2019 · 16 comments
Closed

Use config to define absolute paths #3262

chuchu1313 opened this issue Jan 31, 2019 · 16 comments

Comments

@chuchu1313
Copy link

chuchu1313 commented Jan 31, 2019

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]

@chuchu1313 chuchu1313 changed the title Use tsconfig.json to define baseUrl paths does not work Use config to define absolute paths Jan 31, 2019
@chuchu1313
Copy link
Author

chuchu1313 commented Feb 12, 2019

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';

@ennisbenjamind
Copy link

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?

@chuchu1313
Copy link
Author

@ennisbenjamind No, everything looks good in this solution.

@ennisbenjamind
Copy link

you have any other plugins set up by chance?

@x-yuri
Copy link

x-yuri commented Oct 12, 2019

Alternatively, to reference 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}));
}

@BradRyan
Copy link

BradRyan commented May 28, 2020

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));
}

@marklawlor
Copy link

marklawlor commented Aug 29, 2020

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

ts-node does not support the paths option TypeStrong/ts-node#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

@flybayer
Copy link

flybayer commented Sep 1, 2020

@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()

@alexandermckay
Copy link

alexandermckay commented Oct 15, 2020

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

@marklawlor
Copy link

"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.

@Norfeldt
Copy link

Norfeldt commented Jan 1, 2021

just a follow up on @BradRyan great pointer that led me to a working solution with Create React App (CRA) which uses react-scripts.

this is how my ./cypress/plugins/index.js file ended up looking

/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
const path = require('path')
const findWebpack = require('find-webpack')
const webpackPreprocessor = require('@cypress/webpack-preprocessor')

// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
 * @type {Cypress.PluginConfig}
 */
module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config

  // USE ABSOLUTE PATHS
  // find the Webpack config used by react-scripts - https://github.com/cypress-io/cypress/issues/3262#issuecomment-635550879
  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['@fixtures'] = path.resolve(
    process.cwd(),
    'cypress',
    'fixtures'
  )

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

@bbros-dev
Copy link

@flybayer can you hint where we add this?

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

@bbros-dev
Copy link

@marklawlor, apologies for the TS/JS noob question, but appreciate any hints on what syntax looks like here:

// Add your paths here

@marklawlor
Copy link

marklawlor commented Jun 15, 2021

@marklawlor, apologies for the TS/JS noob question, but appreciate any hints on what syntax looks like here:

// Add your paths here

@bbros-dev https://www.typescriptlang.org/tsconfig#paths

@marklawlor
Copy link

@flybayer can you hint where we add this?

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

Add it as the first line of cypress/plugins/index.ts

@salvag-ntt
Copy link

@flybayer @marklawlor thanks for your effors!
Does your solution work with cypress-cucumber-preprocessor plugin?

I keep getting the usual error

Error: Can't walk dependency graph: Cannot find module '@src/services/api/products' from '/home/gr4ce/workspace/marketplace/cypress/support/application-data.ts'
    required by /home/gr4ce/workspace/marketplace/cypress/support/application-data.ts
 ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants