Cypress - Streamlining login procedures by relocating them to a standalone script

Recently, I delved into the world of Cypress testing and came across a code snippet from an official documentation recipe that I slightly modified:

let token: string;

function fetchUser() {
  cy.request('POST', 'https://my-api/', {
    email: Cypress.env("USER_LOGIN"),
    password: Cypress.env("USER_PASSWORD"),
  })
    .its('body')
    .then((res) => {
      token = res.token;
    });
}

function setUser() {
  cy.visit('/projects', {
    onBeforeLoad(win) {
      win.localStorage.setItem('token', JSON.stringify({ token }));
      for (let i = 0; i < 5; i++) {
        win.localStorage.setItem(`guidedTour${i}`, 'true');
      }
    },
  });
}
before(fetchUser);
beforeEach(setUser);

it('my test', () => {
// some testing happens here
});

The current setup works fine, but I want to refactor fetchUser and setUser into a separate file to avoid redundancy in new test files. However, when I move them to a new file, fetchUser correctly sets the token value, while setUser receives undefined. As a TypeScript beginner with limited experience in handling asynchronous functions, I'm a bit lost. Here's my attempted solution:

authentication.js

export function fetchUser(token: string): string { 
  console.log('fetchUser line 1 ' + token);
  cy.request('POST', 'https://my-api', {
    email: Cypress.env("USER_LOGIN"),
    password: Cypress.env("USER_PASSWORD"),
  })
    .its('body')
    .then((res) => {
      token = res.token;
      return token;
    });
}

export function setUser(token: string) {
  cy.visit('/projects', {
    onBeforeLoad(win) {
      win.localStorage.setItem('token', JSON.stringify({ token }));
      for (let i = 0; i < 5; i++) {
        win.localStorage.setItem(`guidedTour${i}`, 'true');
      }
    },
  });
}

Main Test File

import { fetchUser, setUser } from "../support/authentication";

let token: string;

before(() => {
  token = fetchUser(token);
});
beforeEach(() => {
  setUser(token);
});

it('my test', () => {
// Testing scenarios go here
});

Any advice on how to resolve this issue would be greatly appreciated.

Answer №1

After reviewing your question, my recommendation would be to centralize common functions in the support/commands.js file. The current approach of retrieving the token value in the before hook, storing it in a variable, and using it in the beforeEach hook is not working due to the token being undefined when calling the setUser function.

You can make some adjustments to your code:

support/commands.js

Cypress.Commands.add('fetchUser', () => {
    return cy.request('POST', 'https://my-api', {
        email: Cypress.env("USER_LOGIN"),
        password: Cypress.env("USER_PASSWORD"),
      })
})


Cypress.Commands.add('setUser', (token) => {
    cy.visit('/projects', {
        onBeforeLoad(win) {
          win.localStorage.setItem('token', JSON.stringify({ token }));
          for (let i = 0; i < 5; i++) {
            win.localStorage.setItem(`guidedTour${i}`, 'true');
          }
        },
      })
})

test.spec.js

describe('testing command js', () => {
    beforeEach(() =>{
        cy.fetchUser().then(response => {
            cy.setUser(response.token)
        })
    })

    it('this is test', () => {
        // tests
    })

    it('this is test 2', () => {
        // tests 2
    })
})

I have updated the answer to address the feedback you provided. Depending on whether you want to set the user for each test or once for the entire suite, these methods can be placed in the before and beforeEach hooks accordingly.

Please make any necessary modifications to the code for type checking in Typescript. However, this code should resolve your issue.

Answer №2

Warning: "A function whose declared type is neither 'void' nor 'any' must return a value."

An issue arises from the missing return value in the fetchUser() function. The use of return token changes the chain of Cypress commands but does not actually provide a return value for the function itself. To address this, you should return the cy.request() directly to ensure that the function has a proper return value.

The expected return value would be Chainable<string>, similar to Promise<string>. While you cannot await it, you can access the value using .then(value =>.

Since the request is asynchronous, you cannot set the token variable directly within the function. Instead, you need to return something that indicates the completion of the asynchronous call.

authentification.js

/// <reference types="cypress" />

export function fetchUser(): Chainable<string> {

  return cy.request('POST', 'https://my-api', {
    email: Cypress.env("USER_LOGIN"),
    password: Cypress.env("USER_PASSWORD"),
  })
  .its('body.token');                
}

test

import { fetchUser, setUser } from "../support/authentification";

let token;

before(() => {
  fetchUser().then(val => token = val);    
});

beforeEach(() => {
  setUser(token);                          
});

it('my test', () => {
  ...
});

Warning - Using Aliases

While using aliases is an option to pass the token, keep in mind that Cypress resets aliases between tests, causing issues if multiple tests are involved.

before(() => {
  fetchUser().as('token');
});

beforeEach(() => {
  cy.get('@token').then(token => setUser(token));         
});

it('my test', () => {
  ...      
});

it('my 2nd test', () => {
  ...      
});

Alternatively, setting an alias also creates this.token, which persists across tests and can be used successfully.

before(() => {
  fetchUser().as('token');
});

beforeEach(function() {                
  setUser(this.token);                 
});

it('my test', () => {
  ...      
});

it('my 2nd test', () => {
  ...      
});

Answer №3

Implement a unique command to execute tasks

/cypress/support/authentification.js

function fetchUser() {
  return cy.request('POST', 'https://my-api', {
    email: Cypress.env("USER_LOGIN"),
    password: Cypress.env("USER_PASSWORD"),
  })
  .its('body.token')   
}

function setUser(token) {
  Cypress.on('window:before:load', (win) => {
    win.localStorage.setItem('token', JSON.stringify({ token }));
    for (let i = 0; i < 5; i++) {
      win.localStorage.setItem(`guidedTour${i}`, 'true');
    }
  })
}

Cypress.Commands.add('fetchAndSetUser', () => {
  fetchUser().then(token => {
    setUser(token)
  })
})

/cypress/support/index.js

import './authentification.js'

test

before(() => {
  cy.fetchAndSetUser();
});

beforeEach(() => {                
  cy.visit('/projects')  // onBeforeLoad pre-configured by event 'window:before:load'          
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Leverage the TypeScript Compiler API to verify whether an interface property signature permits the value of undefined (for example, prop1?:

Currently, I am utilizing the TypeScript Compiler API to extract Interface information in order to generate database tables. The process is functioning effectively, however, I am seeking a method to determine if certain fields are nullable, or as it is phr ...

Having trouble with SVG Circles - Attempting to create a Speedometer design

Trying to implement a speedometer in one of the components of my Vue project, but encountering an issue. When I input 0 into my progress calculation for determining the stroke fill, it overlaps with the background circle instead of staying within its bound ...

Error message: Deno package code encounters error due to the absence of 'window' definition

I am facing an issue with a npm package I imported into my Deno project. The code in the package contains a condition: if (typeof window === 'undefined') { throw new Error('Error initializing the sdk: window is undefined'); } Wheneve ...

The TypeScript compiler is unable to locate the identifier 'Symbol' during compilation

Upon attempting to compile a ts file, I encountered the following error: node_modules/@types/node/util.d.ts(121,88): error TS2304: Cannot find name 'Symbol'. After some research, I found that this issue could be related to incorrect target or l ...

Angular virtual scrolling not populating the list with data

I have encountered a challenge while trying to implement infinite virtual scroll on an Angular 7 project with a GraphQL backend from Hasura. I am puzzled by the fact that the new data is not being added and there are multiple API requests being made when ...

What is the best way to dynamically disable choices in mat-select depending on the option chosen?

I was recently working on a project that involved using mat-select elements. In this project, I encountered a specific requirement where I needed to achieve the following two tasks: When the 'all' option is selected in either of the mat-select e ...

Is it possible to swap out the Firestore module `doc` with the `document` module

I enjoy using the Firebase version 9 modules, however, I find that doc is not to my liking. It would be better if it were document, similar to how collection is not shortened to col. The following code does not function as expected: import { doc, collecti ...

Module 'fs' or its type declarations could not be located

I am facing an issue with TypeScript not recognizing the 'fs' module. The error I receive is as follows: Error: src/app/components/drops/drops-map/drops-map.component.ts:9:29 - error TS2307: Cannot find module 'fs' or its correspond ...

Is it possible to define TypeScript interfaces in a separate file and utilize them without the need for importing?

Currently, I find myself either declaring interfaces directly where I use them or importing them like import {ISomeInterface} from './somePlace'. Is there a way to centralize interface declarations in files like something.interface.ts and use the ...

After upgrading from Angular 13 to 14, the <app-root> component is failing to load. Despite no noticeable changes, the issue persists

Having upgraded my Angular 13 app to version 14, I encountered an issue where the page no longer loads properly. Despite checking the configuration and stripping down the index.html file to its basics, the issue persists - nothing seems to be working. Upo ...

Adding TypeScript to your Vue 3 and Vite project: A step-by-step guide

After setting up my project by installing Vue and Vite using the create-vite-app module, I decided to update all the packages generated by 'init vite-app' to the latest RC versions for both Vue and Vite. Now, I am interested in using TypeScript ...

Why isn't the parent (click) event triggered by the child element in Angular 4?

One of my challenges involves implementing a dropdown function that should be activated with a click on this specific div <div (click)="toggleDropdown($event)" data-id="userDropdown"> Username <i class="mdi mdi-chevron-down"></i> </d ...

Combining Auth Observables in Angular: A Complete Guide

Currently, I'm working on an Angular service that leverages AngularFire's auth observable to monitor user state changes. Upon a user signing in, the application should retrieve a user document from MongoDB. To enable components to consume this da ...

Generate a blueprint for a TypeScript interface

In my coding project, I've been noticing a pattern of redundancy when it comes to creating TypeScript interfaces as the code base expands. For example: interface IErrorResponse { code: number message: string } // Feature 1 type FEATURE_1_KEYS = ...

What causes the return value type in a functional interface to be loosely implemented in Typescript?

In an attempt to explain a specific partial type of return value for a functional interface, I have encountered an issue. Within my IStore interface, there is only one property called test. When assigning this interface to the function foo, which returns ...

Utilizing TypeScript for dynamic invocation of chalk

In my TypeScript code, I am trying to dynamically call the chalk method. Here is an example of what I have: import chalk from 'chalk'; const color: string = "red"; const message: string = "My Title"; const light: boolean = fa ...

Utilizing Material-UI with MobileDialog HOC in TypeScript: A Beginner's Guide

I'm running into an issue while trying to implement withMobileDialog in my TypeScript code. Below is the snippet of my code, inspired by a code example from the official documentation. import withMobileDialog, { InjectedProps } from "@material-ui/co ...

eliminate the common elements between two arrays in typescript/javascript

I have two lists of objects, each containing two fields: let users1 = [{ name: 'barney', uuid: 'uuid3'}, { name: 'barney', uuid: 'uuid1'}, { name: 'barney', uuid: 'uuid2 ...

The method toLowerCase is not found on this data type in TypeScript

I am currently working on creating a filter for autocomplete material. Here is an example of my model: export class Country { country_id: number; name: string; } When calling the web method ws: this.ws.AllCountry().subscribe( ...

Iterating through elements within the ng-content directive in Angular using *ngFor

Is it possible to iterate through specific elements in ng-content and assign a different CSS class to each element? Currently, I am passing a parameter to enumerate child elements, but I would like to achieve this without using numbers. Here is an example ...