How to return dimensions of document in Cypress fo

2020-07-22 18:32发布

问题:

I have a function in Cypress support/index.js that is meant to get the dimensions of the cy.document outerWidth and outerHeight, then return them for future use in a test. My problem is that when the test runs and the values are compared with others the assertion says the values are NaN. I checked by console logging the value at the point of the assertion and it was empty, so I must be doing something wrong, I'm just not sure what. My function is below, any help gratefully received, thanks.

function getViewport() {
  var viewport = {}
  cy.document().then((doc) => {
    let width = Cypress.$(doc).outerWidth()
    let height = Cypress.$(doc).outerHeight()
    viewport['bottom'] = height
    viewport['height'] = height
    viewport['left'] = 0
    viewport['right'] = width
    viewport['top'] = 0
    viewport['width'] = width
    viewport['x'] = 0
    viewport['y'] = 0
  }).then(() => {
    return viewport
  })
  return viewport
}

The code that calls getViewport() is

export const getRect = (obj) => {
  var rect
  if (obj == 'viewport') {
    rect = getViewport()
  } else {
    rect = getElement(obj)
    if (Cypress.config('parseLayoutToInt')) { rect = parseAllToInt(rect) }
  }
  return rect
}

And that is called by a custom command, where subject is prevSubject and the element is the string "viewport"

Cypress.Commands.add('isInside', { prevSubject: true }, (subject, element, expected) => {
  var minuend, subtrahend, diff
  minuend = getRect(element)
  subtrahend = getRect(subject)
  diff = getRectDiff(minuend, subtrahend, expected);
  expect(diff).to.deep.equal(expected);
})

回答1:

Like @NoriSte said, the cy commands are asynchronous thus you can't mix them with sync code.

What you want to do is something like:

function getViewport() {
  return cy.document().then( doc => {
    rect = /* do something synchronous */
    return rect;
  });
}

Anyway, to answer the original question (in the title), there's a couple of patterns I use to store a value for later use in cypress:

  1. wrap next commands in the then callback:

    cy.document().then( doc => {
        return doc.documentElement.getBoundingClientRect();
    }).then( viewportRect => {
    
        cy.doSomething(viewportRect);
        cy.doSomethingElse();
    });
    
  2. cache to a variable and access the cached value from inside an enqueued command:

    let viewportRect;
    
    cy.document().then( doc => {
        return doc.documentElement.getBoundingClientRect();
    }).then( rect => viewportRect = rect );
    
    cy.doSomething();
    
    // this is important -- you need to access the `viewportRect`
    // asynchronously, else it will be undefined at the time of access
    // because it's itself assigned asynchronously in the first command'd callback
    cy.then(() => {
        doSomething(viewportRect);
    });
    

Ad the actual problem in your question (if I understood it correctly), I've made a solution you can learn from:

const getRect = (selector) => {
    if (selector == 'viewport') {
        return cy.document().then( doc => {
            return doc.documentElement.getBoundingClientRect();
        });
    } else if ( typeof selector === 'string' ) {
        return cy.get(selector).then( $elem => {
            return $elem[0].getBoundingClientRect();
        });
    // assume DOM elem
    } else {
        return cy.wrap(selector).then( elem => {
            return Cypress.$(elem)[0].getBoundingClientRect();
        });
    }
};

const isInside = (containerRect, childRect) => {
    if ( !containerRect || !childRect ) return false;
    return (
        childRect.top >= containerRect.top &&
        childRect.bottom <= containerRect.bottom &&
        childRect.left >= containerRect.left &&
        childRect.right <= containerRect.right
    );
};

Cypress.Commands.add('isInside', { prevSubject: true }, (child, container, expected) => {
    return getRect(child).then( childRect => {
        getRect(container).then( containerRect => {
            expect(isInside(containerRect, childRect)).to.equal(expected);
        });
    });
});

describe('test', () => {
    it('test', () => {
        cy.document().then( doc => {
            doc.body.innerHTML = `
                <div class="one"></div>
                <div class="two"></div>
                <style>
                    .one, .two {
                        position: absolute;
                    }
                    .one {
                        background: rgba(255,0,0,0.3);
                        width: 400px;
                        height: 400px;
                    }
                    .two {
                        background: rgba(0,0,255,0.3);
                        width: 200px;
                        height: 200px;
                    }
                </style>
            `;
        });
        cy.get('.two').isInside('.one', true);
        cy.get('.one').isInside('.two', false);
    });

    it('test2', () => {
        cy.document().then( doc => {
            doc.body.innerHTML = `
                <div class="one"></div>
                <div class="two"></div>
                <style>
                    body, html { margin: 0; padding: 0 }
                    .one, .two {
                        position: absolute;
                    }
                    .one {
                        background: rgba(255,0,0,0.3);
                        width: 400px;
                        height: 400px;
                    }
                    .two {
                        background: rgba(0,0,255,0.3);
                        width: 200px;
                        height: 200px;
                        left: 300px;
                    }
                </style>
            `;
        });
        cy.get('.two').isInside('.one', false);
        cy.get('.one').isInside('.two', false);
    });
    it('test3', () => {
        cy.document().then( doc => {
            doc.body.innerHTML = `
                <div class="one"></div>
                <style>
                    body, html { margin: 0; padding: 0 }
                    .one {
                        position: absolute;
                        background: rgba(255,0,0,0.3);
                        width: 400px;
                        height: 400px;
                        left: -100px;
                    }
                </style>
            `;
        });
        cy.get('.one').isInside('viewport', false);
    });
});


回答2:

Why there is a synchronous return in your getViewport function? I'm speaking about the last return viewport

function getViewport() {
  var viewport = {}
  cy.document().then((doc) => {
    ...
  })

  return viewport // <-- ?????
}

doing so, all the cy.document().then((doc) etc. code is useless.

I don't know if this is the problem, but I can't run your code locally because it misses a lot of functions. Could you share a "working” GitHub repo to make some more tests?



回答3:

I ran into this problem as well, and opted for a solution with async/await:

function getDocument() {
  return new Promise(resolve => {
    cy.document().then(d => {
      console.log('deeee', d);
      resolve(d);
    });
  });
}

describe('Stuff', () => {
  it('Sees the toasty character', async () => {
    const document = await getDocument();

    // Your test code here
  });
});

Even though Cypress commands aren't really promises, you can create your own promise, and resolve it when ready. Then await that promise in your test code.

Hope it helps!



标签: cypress