Cypress custom command is not recognized when invoked - javascript

I've created the following custom command in my cypress/support/commands.js file.
Cypress.Commands.add("login", (username, password) => {
cy.request({
method: 'POST',
form: true,
url: '/test/login/',
body: {'username': username, 'password': password}
})
})
I had tests passing and login working before moving the login functionality to this custom command. I'm invoking it in my spec with cy.login(testuser, testpwd), but I'm getting the following error message: TypeError: cy.login is not a function. The docs say that /cypress/support/commands.js is loaded before any test files are evaluated, so I assumed that simply placing a custom command in there would make the command available. I'm running the tests through the local (GUI) test runner.

All the code and referenced modules in index.js are loaded before your test file. So you need to refer(require) commands.js in your index.js file.
You can however import commands.js module directly in your test file but then you need to include it every test file.
Recommended approach is to include it in index.js file and you are not worried about explicitly refer in your test files.

To expand on #Dinesh Kumar's excellent answer, it's also important you haven't disabled support for the default support file (I know, unfortunate naming scheme in this case) inside your cypress.json by adding the line: supportFile: false.
Delete that line from your cypress.json if it's there. You can also specify a different path if you're not happy using the default path of cypress/support/index.js.
Working index.js with commands.js file - both in the support folder:
// index.js
const customCommands = require('./commands.js')
module.exports = {
commands: customCommands
}
And double check your settings:
// cypress.json
{
"baseUrl": "http://demo.your-domain.test",
"supportFile": false, // <-- delete this line present
...
}

It may help to put a line import './commands.js' into index.js.

For me it worked when I added the type signature of the custom command in the file cypress/support/index.d.ts. For more information visit: Cypress example - Custom Commands
declare namespace Cypress {
interface Chainable {
clearLocalStorageIframe(): void
}
}
I am using 7.2.0 Cypress and command.ts and index.ts file extension I have changed it to .ts

TL;DR: Check if your IDE tried to resolve cy
I slipped into this problem, because my IDE's autocomplete feature added a dependency to resolve the undeclared cy object – that gets injected by cypress.
const { default: cy } = require('date-fns/esm/locale/cy/index.js');
This was very unfortunate, as there is an ongoing (in 2022) issue with the custom commands and you can find a tons of hints..

Removing
import { cy } from "date-fns/locale";
or similar import
from the test file, resolved this. This gets added automatically to resolve undeclared cy objects
Permanent Solution: Add the following to cypress.json
"compilerOptions": {
"types": ["cypress"]
}

I added at the top of commands.js.
/// <reference types="cypress" />
export{}
//CUSTOM COMMANDS...
This then exported and exposed the custom commands after cy. .

Related

How can I use switch between different environments using the new cypress.config.js in Cypress 10x?

I used to have json files under my config folder containing variables for different environments. For example:
local.env.json would contain:
{
"baseUrl": "localhost:8080"
}
then another one called uat.env.json would contain:
{
"baseUrl": "https://uat.test.com"
}
and its configured on my plugins/index.ts as:
const version = config.env.version || 'uat'; // if version is not defined, default to this stable environment
config.env = require(`../../config/${version}.env.json`); // load env from json
I will then call it on my tests with cy.visit(Cypress.env().baseUrl)) then pass it on the CI with CYPRESS_VERSION=uat npx cypress run
However, with the new Cypress 10x version, the plugin file has been deprecated and just relies on cypress.config.js. I can't find any example on their documentation on how this can be done (I remember they used to have a page with these scenarios but can't find it now).
It's possible to use the old plugins/index.ts in the new cypress.config.ts by importing it.
This is the simplest example (with no new config in cypress.config.ts)
import { defineConfig } from 'cypress'
import legacyConfig from './cypress/plugins/index.js'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:1234',
setupNodeEvents(on, config) {
return legacyConfig(on, config) // call legacy config fn and return result
}
}
})
The typescript and modules may cause you grief about typings etc. I've not tried it in a typescript project, but it does work in a javascript project.
Alternatively, copy/paste everything from plugins/index.ts instead of calling the legacy function. This might be better as you add more plugins in the future.

How to use TypeScript w/ a library with no definition file?

So I’m trying to use the pino-clf library in my koa TS project.
I keep getting this when I try to compile:
TSError: ⨯ Unable to compile TypeScript:
src/modules/logger/index.ts:5:21 - error TS7016: Could not find a declaration file for module 'pino-clf'. '/dev/webservices/node_modules/pino-clf/index.js' implicitly has an 'any' type.
Try `npm i --save-dev #types/pino-clf` if it exists or add a new declaration (.d.ts) file containing `declare module 'pino-clf';`
5 import pinoClf from 'pino-clf'
~~~~~~~~~~
pino-clf doesn’t have a def file and there’s no #types/pino-clf available.
I tried adding a pino-clf.d.ts file in the folder of the file that I’m importing the lib into w/ declare module 'pino-clf' in it. While that got the red squigglies in my IDE to go away, TS still refuses to compile.
How in the world do we use use a lib that’s just plain JS w/ TS and w/o adding a ts-ignore?
So there are two ways this can be accomplished.
Solution One: Probably the easiest
You can just use require ex: const pinoClf = require("pinoClf") - the down-side is that dot reference or intellisense isn't available but if you know the methods you want to use its no biggie.
Solution Two:
Create you own typeDef file in the root of you project. For example,
pino-clf.custom.d.ts
declare module "pino-clf.custom" {
const pinoClfJs = require("pinoClf");
export default class pinoClf {
commonLog (type: string, dest: NodeJS.WriteStream, ancillary: any): void {
pinoClfJs.commonLog(type, dest, ancillary);
}
}
}
then in you tsconfig.json file include the new typeDef file:
{
... // assuming src is already there
"include": [
"src", "pino-clf.custom.d.ts"
]
}
after that you can simply import it import pinoClf from "pino-clf.custom";
This is a very basic implementation, and recommend researching if you desire something more complex. Of course there is more than one way to solve a problem but, I hope this helped. Cheers.

ES6 Dynamic Imports using Webpack and Babel

I've been using Webpack for my ES6 JS project and has been going well until I started to play with dynamic imports.
What I had that worked (router.js):
import { navigo } from "Navigo"; // router
import { clients } from "Controllers/clients.js";
const navigo = new Navigo();
navigo_router.on({
'/clients': () => {
clients.init();
}
});
But the more pages/routes I add, the more imports get stacked up in the head of the module. This is a relatively large app and I have a lot of pages/routes to add and therefore I need to load them dynamically to reduce the size of the initial page load.
So, following Webpack's documentation for dynamic imports, I tried the following which loads the controller module only when the relative route is called:
import { navigo } from "Navigo"; // router
const navigo = new Navigo();
navigo_router.on({
'/clients': () => {
import("Controllers/clients.js").then((clients) => {
clients.init();
});
}
});
But saving this in my editor resulted in a Babel transpiling error; SyntaxError: 'import' and 'export' may only appear at the top level, and clients.init() is not being called when tested in browser.
After a bit of reading, I discovered I needed a Babel plugin to transpile dynamic import() to require.ensure. So, I installed the plugin using the following command:
npm install babel-plugin-dynamic-import-webpack --save-dev
And declared the plugin in my babel.rc file
{ "plugins": ["dynamic-import-webpack"] }
After installing the plugin, the transpiling error disappeared and checking my transpiled code I found that the dynamic import()s has in fact been changed to require.ensure as expected. But now I get the following browser errors when testing:
Error: Loading chunk 0 failed.
Stack trace:
u#https://<mydomain.com>/js/app.bundle.js:1:871
SyntaxError: expected expression, got '<' 0.app.bundle.js:1
Error: Loading chunk 0 failed.
I didn't understand why it was referencing 0.app.bundle.js with the 0. prefix, so I checked my output/dist folder and I now have a new file in there called 0.app.bundle.js:
0.app.bundle.js 1,962bytes
app.bundle.js 110,656bytes
I imagine this new bundled file is the dynamically imported module, clients.js.
I only added dynamic importing to that one route and have left all the other routes as they were. So, during testing, I can view all routes except that one /clients route that now throws the above errors.
I'm totally lost at this point and hoped somebody could help push me over the finish line. What is this new file 0.app.bundle.js and how am I supposed to be using it/including it in my application?
I hope I've explained myself clearly enough and look forward to any responses.
I managed to fix my own problem in the end, so I will share what I discovered in an answer.
The reason the chunk file wasn't loading was because Webpack was looking in the wrong directory for it. I noticed in the Network tab of my developer console that the the chunk file/module was being called from my root directory / and not in /js directory where it belongs.
As per Webpack's documentation, I added the following to my Webpack config file:
output: {
path: path.resolve(__dirname, 'dist/js'),
publicPath: "/js/", //<---------------- added this
filename: 'app.bundle.js'
},
From what I understand, path is for Webpack's static modules and publicPath is for dynamic modules.
This made the chunk load correctly but I also had further issues to deal with, as client.init() wasn't being called and yielded the following error:
TypeError: e.init is not a function
To fix this, I also had to change:
import("Controllers/clients.js").then((clients) => {
clients.init();
});
To:
import("Controllers/clients.js").then(({clients}) => {
clients.init();
});
Note the curly braces in the arrow function parameter.
I hope this helps somebody else.
For debugging, you need to do
import("Controllers/clients.js").then((clients) => {
console.log(clients);
});
maybe working
import("Controllers/clients.js").then((clients) => {
clients.default.init();
});

TypeError: Class extends value undefined is not a constructor or null

This problem has been causing me to lose my sanity for the last couple of days.
Here is my directory structure:
[src]
|- cmds/
| |- Gh.js
| \- Help.js
|- commands.js
|...
I am trying to import a class exported by commands.js into Help.js and Gh.js (and any other files I might add in the future). However, I keep getting an error:
class Gh extends _commands.Command {
^
TypeError: Class extends value undefined is not a constructor or null
All of the files are being transpiled using Babel, with env set to "node": "current" and using the wildcard package. I have tried to set it for "browser" to see if it was an issue of it being too "advanced", but I got a different error about super functions (or something), which I assume is the same issue.
Here is the class being exported from commands.js:
export class Command {
constructor (msg) {
this.id = msg.author.id
this.msg = msg
}
action () {}
get data () {
return readData().user[this.id]
}
updateUserData (key, val) {
updateUserData(this.id, key, val)
}
sendMsg (data) {
sendMsg(this.msg, data)
}
}
...and here is cmds/Gh.js, one of the files that I am trying to import Command into:
import {Command} from '../commands'
export class Gh extends Command {
constructor (msg) {
super(msg)
this.desc = 'Returns GitHub repository link and exits'
}
action () {
this.sendMsg('GitHub link: https://github.com/owm111/knife-wife')
}
}
I tried putting Command into both of the cmds/, and they worked perfectly. However, when moving it back into commands.js, it broke again. I tried changing the path it is importing from from ../commands to ./../commands, ../commands.js, ./../commands.js; none worked. I moving commands.js into cmds/, still broke. I tried to console.log(Command) in both of the cmds/, but they both returned undefined.
All of this makes it look like is a problem with importing, but I cannot figure out what for the life of me. Please help.
If anyone else sees this error, the first thing to look for is circular dependencies. Import file A into B, B into some other file C, and so on. If any of C through Z is imported into A, then JS will not be able to ensure that a file is defined at the time that another file needs it (and will not try to go back and fill in the blanks later).
This was likely the case here, since there was clearly other code not posted, and it only appeared when file dependencies were introduced. The problem exists regardless of file structure: the only structure guaranteed to avoid it is a single giant JS file. The solution is to ensure a strict tree structure of relationships between classes, and use factory methods or alternative communications like emitters to keep the couplings loose.
If you have more than a couple import / require statements, I recommend periodically running a checker like Madge to find and optionally visualize any loops before they become hard to undo.
npm install --save-dev madge
node_modules/madge/bin/cli.js --warning --circular --extensions js ./
As others have mentioned, this results from circular dependencies. I tried for hours to resolve it. Ultimately it was a tool called dpdm that worked wonders for me, finding 27 cycles and quickly leading to resolution. (I only had to solve a couple of those before the rest resolved as well.)
yarn global add dpdm or npm i -g dpdm
Then
dpdm file.js or dpdm file.ts
In my case this found a large number of cycles that Madge and manual inspection had failed to reveal. Great tool.
This is just a simple fix for node.js. Remove export from your class and at the bottom of your file put this in it.
module.exports.Command;
Now if you want to use the command class anywhere you just need to put this in each file where you would like to use it.
var { Command } = require('Command.js');
In my case, I made the dumb mistake of putting parentheses after extending React.Component like this:
class Classname extend React.Component() {
...
Removing the parentheses fixed the error.
In my case: I was importing a non-existing class:
Counter.js:
import React, { Compontent } from 'react';
class Counter extends Compontent {
App.js:
import React from 'react';
import Counter from './components/Counter';
function App() {
Another cause can be that you use the following syntax (after copying a class from a declaration file (xxxx.d.ts) -> xxxx.ts
export declare abstract class Something {
should be (of course):
export abstract class Something {
In my case, the order of the export dependency order was wrong. The dependency was in inheritance. I was using index.ts to export class, interfaces, abstract classes.
If B extends A, and on your index.ts you did the following, this will through the error mentioning A.
export { B } from 'somewhere';
export { A } from 'somewhere';
To fix that, you must maintain the order of dependencies like
export { A } from 'somewhere';
export { B } from 'somewhere';
This is an issue with your node version
What i did was to uninstall my node version manager, re installed and used it to install all my node versions , did nvm use to select which one to use , in my case it was 14.17.3 ,
ran npx react-native init command
That fixed it for me
I got this error that "Sequelize TypeError: Class extends value undefined is not a constructor or null, NodeJS" and solved it using that. If you have the same error, you can use the following solution that I've tried to explain it in code.
for example:
module.exports = (sequelize, DataTypes) => {
// Modeling a table: Inheritance
class Todo extends sequelize.Model {} // (!) Error
// if you want to use the above line, add the following line to "lib\sequelize.js file
// Sequelize.prototype.Model = Model; // add this line here
class Example extends sequelize.Sequelize.Model { }
Example.init({
title: DataTypes.STRING,
description: DataTypes.STRING,
status: DataTypes.BOOLEAN
}, {
sequelize,
modelName: 'todo',
timestamps: true
});
return Example;
};
For people getting this error when using JSweet java to javascript transpiler, I was able to fix it by enabling the 'bundle' option, mentioned here:
Bundle up all the generated code in a single file, which can be used
in the browser. The bundle files are called 'bundle.ts',
'bundle.d.ts', or 'bundle.js' depending on the kind of generated code.
NOTE: bundles are not compatible with any module kind other than
'none'.
This is part of my POM which contains the 'bundle true' addition:
<plugin>
<groupId>org.jsweet</groupId>
<artifactId>jsweet-maven-plugin</artifactId>
<version>${jsweet.transpiler.version}</version>
<configuration>
<verbose>true</verbose>
<tsOut>target/ts</tsOut>
<outDir>target/js</outDir>
<candiesJsOut>webapp</candiesJsOut>
<targetVersion>ES6</targetVersion>
<module>none</module>
<moduleResolution>classic</moduleResolution>
<bundle>true</bundle>
</configuration>
<executions>
<execution>
<id>generate-js</id>
<phase>generate-sources</phase>
<goals>
<goal>jsweet</goal>
</goals>
</execution>
</executions>
</plugin>
Then re-run 'mvn generate-sources', and make sure that you change the index.html file to load the new bundle.js file:
<html>
<head>
<link rel="icon" type="image/png" href="logo.png">
<link rel="shortcut icon" href="logo.png">
<meta charset="utf-8" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
</head>
<body>
<p>Test page</p>
<p id="target"></p>
<script type="text/javascript" src="../webapp/j4ts-0.7.0-SNAPSHOT/bundle.js"></script>
<script type="text/javascript" src="../target/js/bundle.js"></script>
</body>
</html>
I got this error on Angular "class extends value undefined is not a constructor or null" when running cypress and resolved it by changing the compiler options is tsconfig.json.
from:
"compilerOptions": {
"baseUrl": "./",
"downlevelIteration": true,
"resolveJsonModule": true,
"paths": {
"*": [
"./node_modules/*" <== throws error
],
to:
"compilerOptions": {
"baseUrl": "./",
"downlevelIteration": true,
"resolveJsonModule": true,
"paths": {
"*": [
"./node_modules/" <== correct
],
Another common cause of this error is if you installed Node.js into an already existing folding of an older installation of Node.js. Removing and reinstalling Node.js fixes that issue.
I got this error when I upgraded to Angular 13 from 12. ng update did not update webpack to v5 and their upgrade checklist doesn't mention it being a retirement. Once I upgraded webpack to latest, the error went away for me.
Anyone is facing this issue even though there is no circular dependency and using webpack then please add target:node in your webpack config file.

Loading hooks in CucumberJS with Protractor and Gulp

I setup CucumberJS with Protractor and Gulp. I followed the documentation available here:
https://github.com/cucumber/cucumber-js
I have my feature file and step definition file. I also created world.js file in support folder and it is loaded in my step definition file with:
this.World = require("../support/world.js").World;
So the same way as it is presented in the documentation.
Everything works till this moment.
I tried to add some cucumber hooks to my case. I created hooks.js file in the support folder as it is proposed in the documentation, so:
// features/support/hooks.js (this path is just a suggestion)
var myHooks = function () {
this.Before(function (callback) {
// Just like inside step definitions, "this" is set to a World instance.
// It's actually the same instance the current scenario step definitions
// will receive.
// Let's say we have a bunch of "maintenance" methods available on our World
// instance, we can fire some to prepare the application for the next
// scenario:
console.log("Before hook");
// Don't forget to tell Cucumber when you're done:
callback();
});
};
module.exports = myHooks;
The documentation does not say how this hook.js file should be loaded in my step definitions so I assume that it is somehow loaded with the "convention over configuration" approach. Unfortunately, the file is not loaded and the Before method is not executed.
Any ideas?
If hooks are NOT in the same folder as your step_definitions, you would need to explicitly specify where your hooks are using --require. For example,
cucumber.js test/functional/features/xyz.feature
--require test/functional/step_definitions/
--require features/support/ --format=pretty
To avoid this, I usually keep my hooks under step_definitions folder. Since you need to specify require for step_definitions anyways, you don't need to explicitly specify require for hooks. So lets say if your hooks are in test/functional/step_definitions/, with following your hooks should get invoked.
cucumber.js test/functional/features/xyz.feature
--require test/functional/step_definitions/
--format=pretty
Once you have your hooks.js file, go to your cucumberOpts inside of your protractor.conf.js file and add the path to your hooks.js file there, that's it, your hooks.js file will be loaded.
cucumberOpts: {
require: [
conf.paths.e2e + '/steps/**/*Steps.js',
conf.paths.e2e + '/utilities/hooks.js',
],
tags: ['~#wip', '~#manual'],
format: 'pretty'
}
You can also include console.log('Was my hook loaded') in your hooks.js file and search for that log text later to ensure your hook was properly loaded.

Categories

Resources