Search code examples
typescriptcypresssinonstub

Handle cypress new tab with typescript


I am new to Cypress and I am trying to handle the usage of new application tabs.

I have a button without a link

When clicking on this button, a new tab is opened with a random id in the URL (eg. '/ticket/{some-id}')

I have a Cypress test project with typescript. I am trying to take that link of the new tab and use it in the current test runner browser.

I have followed the examples from https://glebbahmutov.com/blog/cypress-tips-and-tricks/#deal-with-windowopen but I have some errors:

  1. Error for .wrappedMethod: https://i.sstatic.net/Sq2Yq.png : Property 'wrappedMethod' does not exist on type '((url?: string | URL, target?: string, features?: string) => Window) & ((url?: string | URL, target?: string, features?: string) => Window)'.

  2. Error for .as('open'): https://i.sstatic.net/E3u2b.png : Property 'as' does not exist on type 'SinonStub<any[], any>'.

Note: This error is happening only for .ts files. The method works for .js file. But I need to have the tests in typescript

Could someone please help me understand if I need some specific dependencies/ configuration to make it work?

This is my code: tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": "../node_modules",
    "target": "es6",
    "module": "commonjs",
    "allowJs": true,
    "lib": ["es6", "dom", "dom.iterable"],
    "types": ["cypress", "@testing-library/cypress", "node"]
  },
  "include": ["**/*.ts", "cypress.config.ts.test"]
}

package.json:

{
  "name": "E2E test suite",
  "version": "1.0.0",
  "description": "Cypress e2e tests",
  "main": "index.js",
  "scripts": {
    "cy:open": "cypress open",
    "cy:run": "cypress run",
    "format": "prettier . --write"
  },
  "dependencies": {
    "@cspell/eslint-plugin": "^8.4.1",
    "dotenv": "^16.0.0",
    "faker": "^6.6.6",
    "npx": "^10.2.2"
  },
  "devDependencies": {
    "@bahmutov/cy-api": "^2.2.6",
    "@cypress/puppeteer": "^0.1.3",
    "@faker-js/faker": "^8.4.1",
    "@testing-library/cypress": "^10.0.1",
    "@typescript-eslint/eslint-plugin": "^7.1.0",
    "@typescript-eslint/parser": "^7.1.0",
    "chai": "^5.0.3",
    "cypress": "^13.7.0",
    "cypress-await": "^1.6.2",
    "cypress-codegen": "^2.0.0",
    "cypress-wait-until": "^3.0.1",
    "eslint": "^8.57.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-prettier": "^5.1.3",
    "prettier": "^3.2.5",
    "typescript": "^4.2.3"
  }
}

test.cy.ts class:

import { parkingLotAccessPage } from '../../support/pageObjects/pageElements/parkingLot/parking-lot-access.page';

describe('Parking Lot Access', () => {
  beforeEach(() => {
    cy.visit('/');
  });

  it('should successfully access parking lot while still available spaces', function () {
    cy.window().then((win) => {
      cy.stub(win, 'open')
        .callsFake((url, target) => {
          expect(target).to.be.undefined;
          return win.open.wrappedMethod.call(win, url, '_self');
        })
        .as('open');
    });
    parkingLotAccessPage.openBarrierButton().click();
  });
});

parkingLotAccessPage.page.ts Page Object:

class ParkingLotAccessPage {
  private elements = {
    openBarrierButton: () => cy.get('.barrier-button'),
  };

  get openBarrierButton() {
    return this.elements.openBarrierButton;
  }
}

export const parkingLotAccessPage = new ParkingLotAccessPage();

cypress.config.ts:

import { defineConfig } from 'cypress';
require('dotenv').config();

export default defineConfig({
  screenshotOnRunFailure: false,
  video: false,
  defaultCommandTimeout: 10000,

  retries: {
    runMode: 2
  },

  env: {
    apiUrl: process.env.BASE_API ?? 'http://localhost:8080',

    userPassword: process.env.USER_PASSWORD,
    userName: process.env.USER_NAME,

    adminPassword: process.env.ADMIN_PASSWORD,
    adminUserName: process.env.ADMIN_NAME
  },

  e2e: {
    baseUrl: process.env.BASE_URL ?? 'http://localhost:3000',
    supportFile: 'cypress/support/e2e.ts'
  }
});

Solution

  • The cy.stub() command uses Sinon under the hood, so the first thing you need is the sinon types:

    npm install --save @types/sinon
    

    and add in tsconfig.json

    {
      "compilerOptions": {
        ...
        "types": ["cypress", "@testing-library/cypress", "node", "@types/sinon/index.d.ts"]
    

    Then in the test you can solve #1 wrappedMethod with a cast, and #2 .as() by reversing the chaining order

    cy.window().then((win) => {
      cy.stub(win, 'open')
        .as('open')
        .callsFake((url, target) => {
          expect(target).to.be.undefined;
          return (win.open as sinon.SinonStub).wrappedMethod.call(win, url, '_self')
        })
    })
    

    A Reproducible Example

    Here is the Cypress/Typescript test with the code to trigger window.open made generic (will work without the app)

    cy.window().then(win => {
      cy.stub(win, 'open')
        .as('open')
        .callsFake((url, target) => {
          expect(target).to.be.undefined
          return (win.open as sinon.SinonStub).wrappedMethod.call(win, url, '_self')
        })
    })
    
    cy.window().then(win => {
      win.open('https://example.com')
    })
    
    cy.get('@open')
      .should('have.been.calledOnceWithExactly', 'https://example.com')
    

    enter image description here