0

I am trying to overwrite Cypress commands such as click, type and should to include some waiting time before they are executed. My motivation for this is that I want to highlight the areas the test interacts with in the produced video, so in click I would like to say for example: "Display circle where the click will happen, wait 500ms, click, wait 250ms, remove circle".

The wait-part of this of this is what causes me trouble.

Google suggests I do something like this:

Cypress.Commands.overwrite('click', function (originalFN) {
  const originalParams = [...arguments].slice(1);
  cy.wait(500).then(() => originalFN.apply(originalFN, originalParams));
});

And I think this works for normal clicks(), but it causes the type command to fail entirely saying this: Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.

It seems type() internally calls click in a way that prevents me from using wait() inside click.

Is there any way around this?

Klompus
  • 130
  • 1
  • 9
Cola_Colin
  • 415
  • 5
  • 16

2 Answers2

4

You can also try a simple setTimeout() instead of cy.wait().

Since setTimeout() is not a Cypress command, you would not get this error.

Cypress.Commands.overwrite('click', function (originalFN) {
  const originalParams = [...arguments].slice(1);
  setTimeout(() => {
    originalFN.apply(originalFN, originalParams)
  }, 500)
})
Lola Ichingbola
  • 3,035
  • 3
  • 16
-1

I've found a solution in the code of a library to slow down cypress, the key is to overwrite the internal runCommand cypress function. This allows me to do what I want on click and type. Should is still an open question, but not as important. Code below is my function to patch cypress, which I call right before my tests.

export function patchCypressForVideoRecording(cy: any, Cypress: any, speedFactor = 1) {
  const colorClick = 'rgba(255, 50, 50, 0.8)';
  const colorType = 'rgba(50, 255, 50, 0.8)';
  const colorShould = 'rgba(50, 50, 255, 0.8)';

  const waitTime = 600;

  const highlightArea = (rect: any, clr: string, scale: boolean) => {
    const x = Math.round(rect.x);
    const y = Math.round(rect.y);
    const w = Math.round(rect.width);
    const h = Math.round(rect.height);

    // cy.window() breaks in commands like click due to promise-inside promises stuff
    // this window reference is just there and allows to run synchronous side-effects js without cypress noticing it
    const hackWindow = (cy as any).state('window');
    hackWindow.eval(`
        const time = ${waitTime / speedFactor};
            
        const x = ${x};
        const y = ${y};
    
        const highlightElement = document.createElement('div');
        highlightElement.style.backgroundColor = '${clr}';
        highlightElement.style.position = 'fixed';
        highlightElement.style.zIndex = '999';
        highlightElement.style['pointer-events'] = 'none';
    
        document.body.appendChild(highlightElement);
    
        const scaleElement = (p) => {
            if (${scale}) {
                const psq = p;
    
                const scale = (0.1 + ((psq < 0.5 ? (1 - psq) : psq)));
    
                const w = scale * ${w};
                const h = scale * ${h};
                
                const wLoss = ${w} - w;
                const hLoss = ${h} - h;
        
                const x = ${x} + wLoss / 2;
                const y = ${y} + hLoss / 2;
        
                return {x, y, w, h};
            } else {
                const w = ${w};
                const h = ${h};
                
                const x = ${x};
                const y = ${y};
        
                return {x, y, w, h};
            }
        };
    
        const newSize = scaleElement(0);
        highlightElement.style.top = newSize.y + 'px';
        highlightElement.style.left = newSize.x + 'px';
        highlightElement.style.width = newSize.w + "px";
        highlightElement.style.height = newSize.h + "px";
    
        const tickSize = 30;
    
        let op = 1;
        let prog = 0;
        const fadeIv = setInterval(() => {
            prog += tickSize;
    
            const p = Math.min(1, prog / time);
    
            let op = 1-(p*0.5);
    
            highlightElement.style.opacity = op + '';
    
            const newSize = scaleElement(p);
            highlightElement.style.top = newSize.y + 'px';
            highlightElement.style.left = newSize.x + 'px';
            highlightElement.style.width = newSize.w + "px";
            highlightElement.style.height = newSize.h + "px";
    
        }, tickSize);
    
        setTimeout(() => {
            clearInterval(fadeIv);
            document.body.removeChild(highlightElement);
        }, time);
      `);
  };

  const highlightInteractedElements = (firstParam: any, clr: string, scale: boolean) => {
    if (firstParam != null && firstParam.length != null && firstParam.length > 0 && typeof firstParam !== 'string') {
      for (let i = 0; i < firstParam.length; i++) {
        const elem = firstParam[i];
        if (elem != null && 'getBoundingClientRect' in elem && typeof elem['getBoundingClientRect'] === 'function') {
          highlightArea(elem.getBoundingClientRect(), clr, scale);
        }
      }
    }
  };

  // To figure out the element that is clicked/typed in need to wait until
  // the selector right before click/type has a subject element
  const waitAndDisplay = (x: any, clr: string) => {
    if (x.state === 'passed') {
      highlightInteractedElements(x.attributes.subject, clr, true);
    } else {
      if (x.attributes.prev.state === 'queued') {
        setTimeout(() => {
          waitAndDisplay(x, clr);
        }, 15);
      } else {
        highlightInteractedElements(x.attributes.prev.attributes.subject, clr, true);
      }
    }
  };

  const cqueue = (cy as any).queue;
  const rc = cqueue.runCommand.bind(cqueue);

  cqueue.runCommand = (cmd: any) => {
    let delay = 50;

    if (cmd.attributes.name === 'click') {
      waitAndDisplay(cmd, colorClick);

      delay = waitTime / 2;
    }

    if (cmd.attributes.name === 'type') {
      waitAndDisplay(cmd, colorType);

      delay = waitTime;
    }

    return Cypress.Promise.delay(delay / speedFactor)
      .then(() => rc(cmd))
      .then(() => Cypress.Promise.delay(delay / speedFactor));
  };

  Cypress.Commands.overwrite('should', function (originalFN: any) {
    const originalParams = [...arguments].slice(1);

    highlightInteractedElements(originalParams[0], colorShould, false);

    return originalFN.apply(originalFN, originalParams);
  });
}
Cola_Colin
  • 415
  • 5
  • 16