Search code examples
cypresscartvisibilityshopping-cart

Cypress testing issue: Cart modal not visible


I'm forwarding the code containing the exemplary working steps, finishing with clicking the Add to Basket button.
Steps after that should consider that a cart modal appears, and Cypress should click the Proceed to Checkout button. This is where the issue appears. No matter what I've tried Cypress couldn't find the cart modal on the page. I get various issues like CSS not visible, opacity 0 and so. While manually testing the page, I've noticed that the cart modal disappears pretty fast after clicking on the Add to Basket button so I guess it can be a possible reason why Cypress can't find it.

I have referred to a blog article written by Gleb Bahmutov https://glebbahmutov.com/blog/flaky-iframe-test/, but I couldn't really find the answer there, and I'm not that experienced in test automation, to be honest :)

Cypress.on('uncaught:exception', (err, runnable) => {
  // Return false to prevent the test from failing
  return false;
});
describe('Iqos Shop Test', () => {
  it('Add a product in the basket and proceed to checkout', () => {
    // Visit the specified URL
    cy.visit('https://www.iqos.com/gb/en/home.html?gr=false');

    // Select the month from the dropdown
    cy.get('#dropdownMonths')
      .parent().click();
    cy.get('#sag-month-01').click({ force: true });

    // Select the year from the dropdown
    cy.get('#dropdownYears')
      .parent().click();
    cy.get('#sagyear1999').click({ force: true });

    // Click the confirm button
    cy.get('span.sav-btn-text:contains("Confirm")').click();
    
    // Click on the promotion button on the main banner
    cy.get('.btn-white-turquoise').eq(0).click();
    
    // Buy IQOS ILUMA ONE Starter Kit
    cy.get('a[href="/gb/en/shop/iqos-iluma-one-starter-kit.html"]')
      .should('be.visible')
      .eq(0)
      .click();
  
    // click on the "Add to cart" button
    cy.get('section.product-info__wrapper')
      .find('div.product-detail__react--container')
      .should('be.visible')
      .wait(200)
      .within(() => {
       cy.contains('button', 'Add to basket').should('be.visible').click({ force: true })
      });
      
  });
});

Here, I have copied the Cart modal element:

 <div class="minicart__container" id="minicart" aria-label="Minicart" aria-live="polite" aria-relevant="additions removals" tabindex="-1" data-minicart-show-duration="7000" style="top: 139px;"><div class="minicart__list-wrapper"><div class="minicart__actions"><div class="add-to-cart--container"><svg class="message-icon" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" fill="#93b933" r="16"></circle><path d="m8 17.2337322 4.2303003 4.7662678 11.7696997-12" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5"></path></g></svg><span class="add-to-cart">Item added to basket</span></div><button class="btn btn--icon btn-close link-device__header--btn"><svg class="icon-close" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><g fill="none" fill-rule="evenodd" stroke="#545454" stroke-linecap="round" stroke-width="1.5"><path d="M15.485 7L-1.485 7" transform="translate(1 1) rotate(-135 7 7)"></path><path d="M15.485 7L-1.485 7" transform="translate(1 1) rotate(135 7 7)"></path></g></svg></button></div><ul class="minicart__products-list"><li class="minicart__bundle"><div class="product__name">IQOS Iluma One Starter Kit</div><div class="minicart__item"><div class="badge-container"></div><div class="minicart__product"><div class="minicart__product-left"><img class="minicart__product-img" src="/vanity/content/pmisite/gb/en/.rrp.G0000590.00.144x70.jpg/G0000590-2023-11-29T13:52:49.386.jpg" alt="IQOS ILUMA One Kit Pebble Beige 00" title="IQOS ILUMA One Kit Pebble Beige"><div class="minicart__product-info"><a href="/gb/en/shop/iluma-one-kit-pebble-beige.html" class="minicart__product-title"><span>IQOS ILUMA One Kit Pebble Beige</span></a><div class="minicart__product-bottom"><p class="minicart__product-qty"><span class="minicart__qty-label">Quantity</span><span class="minicart__qty-value">1</span></p></div></div></div><div class="minicart__product-right"><div class="minicart__product-price"><span class="minicart__discount-value">£29.00</span><span class="minicart__price-value">£17.00</span></div></div></div></div><div class="minicart__item"><div class="badge-container"></div><div class="minicart__product"><div class="minicart__product-left"><img class="minicart__product-img" src="/vanity/content/pmisite/gb/en/.rrp.G0000692.00.144x70.jpg/G0000692-2023-11-29T13:52:49.386.jpg" alt="TEREA AMBER PACK 00" title="TEREA AMBER PACK"><div class="minicart__product-info"><a href="/gb/en/shop/terea-amber-pack.html" class="minicart__product-title"><span>TEREA AMBER PACK</span></a><div class="minicart__product-bottom"><p class="minicart__product-qty"><span class="minicart__qty-label">Quantity</span><span class="minicart__qty-value">2</span></p></div></div></div><div class="minicart__product-right"><div class="minicart__product-price"><span class="minicart__price-value">£12.00</span></div></div></div></div></li></ul></div><div class="minicart__order-summary"><div class="minicart__buttons other"><a class="minicart__one-click global-btn btn--dark" href="#"><span data-pmi-el="minicart-proceed-to-checkout" class="minicart__btn-label btn__label">Proceed to checkout</span></a><a href="#" class="minicart__view-basket global-btn btn--light"><span data-pmi-el="minicart-view-basket-button" class="minicart__btn-label btn__label">View cart</span><span class="minicart__btn-label btn__label minicart-item__number">&nbsp;(3)</span></a></div><div class="minicart__info"></div></div></div>

Solution

  • I had a go at this, I think the main problem is the modal is slow to come into full visibility.

    It looks like the following things are blocking

    • one or two API calls are made after the product is added to the cart and the page waits for the response before the modal is shown.

    • there is some animation attached to the modal, indicated by opacity: 0, which is the start of the animation, which then gets increased to opacity: 1 for a fade-in effect.

    • the parent #nbw__header--desktop has display: none which is changed after the API call response arrives

    Essentially, I added 30 seconds timeout to the modal query, and this seems to fairly consistently pass the test.

    In the example below, I moved the visit and age-consent into a beforeEach to show a more concise log.

    Also added cy.intercept({ resourceType: /xhr|fetch/ }, { log: false }) to remove the noisy XHR logs.

    Then I bumped up the viewport to reduce issue of elements being invisible because they are off-screen.

    Some other elements I changed to search by text, since the class-based selectors aren't the best, as the color themes may be changed.

    beforeEach(() => {
      cy.intercept({ resourceType: /xhr|fetch/ }, { log: false })
      cy.viewport(2000, 2000)
      cy.visit('https://www.iqos.com/gb/en/home.html?gr=false');
      cy.get('#dropdownMonths').click();
      cy.contains('January').click()
      cy.get('#dropdownYears').click();
      cy.contains('1999').click()
      cy.contains('span.sav-btn-text', 'Confirm').click();
      cy.contains('Buy from £29', {timeout:10_000}).click() // Click on the promotion button
    })
    
    it('Add a product in the basket and proceed to checkout', () => {
      cy.get('a[href="/gb/en/shop/iqos-iluma-one-starter-kit.html"]').eq(0).click();
      cy.get('#onetrust-reject-all-handler').click()  // get rid of cookies popup
      cy.get('section.product-info__wrapper')
        .find('div.product-detail__react--container')
        .should('be.visible')
      cy.contains('button', 'Add to basket').should('be.visible').click()
    
      cy.get('#minicart', {timeout:30_000}).should('be.visible')
      cy.contains('Proceed to checkout').click()
    
      // confirm checkout is visible
      cy.contains('Welcome to secure checkout', {timeout:10_000}).should('be.visible')
    })
    

    enter image description here


    You could also look into intercepting and waiting on the API calls as an alternative to adding a long timeout.