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

Cannot find module ./cypress/commands/cypress/commands/ #191

Closed
emahuni opened this issue Jul 10, 2023 · 7 comments
Closed

Cannot find module ./cypress/commands/cypress/commands/ #191

emahuni opened this issue Jul 10, 2023 · 7 comments

Comments

@emahuni
Copy link

emahuni commented Jul 10, 2023

I am getting the following error:

Cannot find module './cypress/commands/cypress/commands/auth-commands.ts'

I have traced the error from the following line in src/plugin/index.ts:

  on('task', {
    importCustomCommands: () => {
      return {
        filePaths: globSync(`${COMMANDS_DIRECTORY}/**/*`, { nodir: true }),
        commandsDirectory: `cypress${sep}commands${sep}`
      };
    }
  });

to another line in src/index.ts:

 const customCommandObject = require(`../../../${projectName}cypress/commands/${filePath.replace(
        commandsDirectory,
        ''
      )}`);

Notice that sep depends on operating system in node context, but somehow I think that is different with the globing that happens, because I am on Windows and running the whole thing in bash. I think there should be a mix-up of paths somewhere between bash and the OS. To resolve this, I suggest that the commands path be configurable when one configs the plugin itself to make it less opinionated and prone to breaking in different envs.

For now doing the following should be fine.

  on('task', {
    importCustomCommands: () => {
      return {
        filePaths: globSync(`${COMMANDS_DIRECTORY}/**/*`, { nodir: true }),
        commandsDirectory:  new RegExp(`cypress[\\\\\\/]commands[\\\\\\/]`, 'g')
      };
    }
  });

This allows the replace statement used in beforeAll to replace the path regardless of the operating system's sep, hence the import statement can also be safely removed.

@emahuni
Copy link
Author

emahuni commented Jul 10, 2023

I think I have a solution to the actual problem, there are several reasons why we are importing using that weird require syntax. The import is happening from the plugin's directory node_modules/cypress-codegen/dist/, so, that is backing out into the main dir. ../../../. Then I don't see why we are replacing the filePath in the first place; a simpler:

const customCommandObject = require(`../../../${projectName}${filePath}`);

would do since the file path is from the main dir into the commands dir.

I have noticed that my setup will not work with this plugin, at least as it is, since the project is a pnpm workspaces package. So that path is now so lost that reconciling it has been a headache. It comes out as a ../../../../../../packages/project/cypress/commands as it has to backoff three more symlink levels to the root workspace dir, then figure out the project... Webpack was simply not happy with anything I tried.

What I ended up doing was taking the bit of code that adds the commands to cy and do that from the e2e script instead. I removed all the plugin configs and inits.

I wish this plugin could just have such an export so as to allow manual imports and addition of commands if the auto commands addition doesn't work, such as in my case.

function isScopedMethod (methodName?: string) {
    return !!methodName && methodName.endsWith('Scoped');
}

export function addModuleCommands (module) {
    const methodNames = Object.keys(module);
    methodNames.forEach((commandName: string) => {
        const method = module[commandName];
        if (isScopedMethod(commandName)) {
            // @ts-ignore
            Cypress.Commands.add(commandName, { prevSubject: 'element' }, method);
        } else {
            // @ts-ignore
            Cypress.Commands.add(commandName, method);
        }
    });
}

then I used in a cypress/commands/index.ts file:

import { addModuleCommands } from "cypress-codegen";

// then manually import the command files
import * as authCommands from "./auth.coms"; 
import * as cyGetter from './cy-getter.coms';
import * as delaysTimeouts from './delays-timeouts.coms';

/** add commands to cypress */
addModuleCommands(authCommands);
addModuleCommands(cyGetter);
addModuleCommands(delaysTimeouts);

Which is then imported by e2e.ts as import '../commands'.

I don't mind doing this, it's fine and much better than doing the original Cypress recipe of adding functions.

How to solve the auto import?
The issue is with webpack. What I noticed I could do was import the commands' modules fromsrc/plugin/index.ts file then send them over to the src/index.ts file as already imported modules. This means totally removing the 2 params sent to the beforeAll function. But doing this needs the modules to be serialized then deserialized on the other end for addition to cy. I didn't have time to write that out, but I think that's the overall solution that can make this easier to maintain without the weird opinionated require path resolution hack. It shows it's hacky when the maintainers clearly state they don't know why they have to do it that way.

 // This relative file path is extremely particular and for some unknown reason must be exactly this.
      const customCommandObject = require(`../../../${projectName}cypress/commands/${filePath.replace(
        commandsDirectory,
        ''
      )}`);

Well I have stated the reason, but solving this to work seamlessly in other environments requires a huge change from the way this is being done.

@danadajian
Copy link
Contributor

@emahuni Thanks for pointing this out! I do realize this plugin does not currently work with pnpm because of the require() statement, and also doesn't work with esbuild. I would like to refactor this to add support for both of these, since webpack is basically obsolete these days. However, I could see we would still get some value from simply importing a function that just calls Cypress.Commands.add() on everything in a module.

We can release a v2.0.0 that implements your addModuleCommands() idea. If you would like to contribute this I would encourage you to do so!

@danadajian
Copy link
Contributor

@emahuni Check out version 2.0.0, which contains substantial improvements!

@emahuni
Copy link
Author

emahuni commented Aug 16, 2023

New version 🎉

Sorry I spent time doing something and completely forgot to get back to this, by the time you asked me that question I had already changed a lot of things I intended to contribute back to this repo and left this unread, guess got too busy. I'll check out the new release, I hope you used the proposal it was okay to use the ideas. I'll share what I did in a new issue so we can improve on it. It even simplifies commands that expect a subject to not require a scope name.

@emahuni
Copy link
Author

emahuni commented Aug 16, 2023

I love the new version. That's even better!

@emahuni
Copy link
Author

emahuni commented Aug 16, 2023

I didn't see any scooped examples, are they still supported?

@danadajian
Copy link
Contributor

I didn't see any scooped examples, are they still supported?

We can maybe add support later, but for now I'd recommend adding those commands separately with the desired prevSubject option. It would be difficult to support all use cases since there are many options Cypress offers for adding custom commands.

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

2 participants