Skip to content

Instantly share code, notes, and snippets.

@greg-hoarau
Last active December 17, 2022 15:49
Show Gist options
  • Save greg-hoarau/5e6b705ccd24b06a85ba45f226f20558 to your computer and use it in GitHub Desktop.
Save greg-hoarau/5e6b705ccd24b06a85ba45f226f20558 to your computer and use it in GitHub Desktop.
switchToFrame custom command for Cypress
Cypress.Commands.add('switchToFrame', { prevSubject: 'element' }, ($iframe, callback = () => {}) => {
Cypress.log({
name: 'switchToFrame',
message: '',
});
const waitForFrameLoading = () => {
return new Cypress.Promise((resolve, reject) => {
onIframeReady($iframe[0], resolve, reject);
});
};
cy.wrap(null, { log: false }).then(() => {
// return a promise to cy.then() that
// is awaited until it resolves
return waitForFrameLoading().then((iframeDoc) => {
expect(iframeDoc).to.exist;
cy.wrap(iframeDoc, { log: false }).within({ log: false }, callback);
});
});
});
/**
* Callback when the iframe is ready
* @callback onIframeReadySuccessCallback
* @param {HTMLDocument} contents - The contentDocument of the iframe
*/
/**
* Callback if the iframe can't be accessed
* @callback onIframeReadyErrorCallback
*/
/**
* Calls the callback if the specified iframe is ready for DOM access
* @param {HTMLIFrameElement} ifr - The iframe DOM element
* @param {onIframeReadySuccessCallback} successFn - Success
* callback
* @param {onIframeReadyErrorCallback} errorFn - Error callback
* @see {@link http://stackoverflow.com/a/36155560/3894981} for
* background information
*/
const onIframeReady = (ifr, successFn, errorFn) => {
try {
if (isIframeReadyStateComplete(ifr)) {
if (isIframeBlank(ifr)) {
observeIframeLoad(ifr, successFn, errorFn);
} else {
getIframeContents(ifr, successFn, errorFn);
}
} else {
observeIframeLoad(ifr, successFn, errorFn);
}
} catch (e) {
// accessing document failed
errorFn();
}
};
/**
* Checks if an iframe readyState is complete
* @param {HTMLIFrameElement} ifr - The iframe DOM element
* @return {boolean}
*/
const isIframeReadyStateComplete = (ifr) => {
try {
return ifr.contentWindow.document.readyState === 'complete';
} catch (err) {
return false;
}
};
/**
* Checks if an iframe is empty (if about:blank is the shown page)
* @param {HTMLIFrameElement} ifr - The iframe DOM element
* @return {boolean}
*/
const isIframeBlank = (ifr) => {
const bl = 'about:blank',
src = ifr.getAttribute('src').trim(),
href = ifr.contentWindow.location.href;
return href === bl && src !== bl && !!src;
};
/**
* @callback observeIframeLoadErrorCallback
*/
/**
* Observes the onload event of an iframe and calls the success callback or
* the error callback if the iframe is inaccessible. If the event isn't
* fired within the specified Cypress.config('defaultCommandTimeout'),
* then it'll call the error callback too
* @param {HTMLIFrameElement} ifr - The iframe DOM element
* @param {getIframeContentsSuccessCallback} successFn
* @param {observeIframeLoadErrorCallback} errorFn
*/
const observeIframeLoad = (ifr, successFn, errorFn) => {
let called = false,
tout = null;
const listener = () => {
if (called) {
return;
}
called = true;
clearTimeout(tout);
try {
if (!isIframeBlank(ifr)) {
ifr.removeEventListener('load', listener);
getIframeContents(ifr, successFn, errorFn);
}
} catch (e) {
// isIframeBlank maybe throws throws an error
errorFn();
}
};
ifr.addEventListener('load', listener);
tout = setTimeout(listener, Cypress.config('defaultCommandTimeout'));
};
/**
* @callback getIframeContentsSuccessCallback
* @param {HTMLDocument} contents - The contentDocument of the iframe
*/
/**
* @callback getIframeContentsErrorCallback
*/
/**
* Calls the success callback function with the iframe document. If it can't
* be accessed it calls the error callback function
* @param {HTMLIFrameElement} ifr - The iframe DOM element
* @param {getIframeContentsSuccessCallback} successFn
* @param {getIframeContentsErrorCallback} [errorFn]
* @access protected
*/
const getIframeContents = (ifr, successFn, errorFn = () => {}) => {
let doc;
try {
const ifrWin = ifr.contentWindow;
doc = ifrWin.document;
if (!ifrWin || !doc) {
// no permission = null. Undefined in Phantom
throw new Error('iframe inaccessible');
}
} catch (e) {
errorFn();
}
if (doc) {
successFn(doc);
}
};
cy.get('#iframe')
.should('exist')
.switchToFrame(() => {
cy.get('#buttonInIframe').click();
});
@RahulARanger
Copy link

Thank you 😊. Test cases are looking pretty with switchToFrame and resemble like selenium

@MinhNguyenElca
Copy link

MinhNguyenElca commented Jun 23, 2022

Hi,

I applied your custom command for iframe. However, if I have something navigate to another URL inside the iframe, it's impossible to get any element anymore. Do you have any solution for that?

eg.

cy.get('[id=page-RateSelection] iframe').switchToFrame(() => {
            cy.get('[class*=performances_group_container]')
                .find('[class*=performance][class*=available]')
                .first()
                .click(); // this action will change the parent URL
            cy.get('[id=page_selection_eventSeat]').should('be.visible'); // this one will not  be available
        });

Thanks for your help.

@greg-hoarau
Copy link
Author

H

Hi,

I applied your custom command for iframe. However, if I have something navigate to another URL inside the iframe, it's impossible to get any element anymore. Do you have any solution for that?

eg.

cy.get('[id=page-RateSelection] iframe').switchToFrame(() => {
            cy.get('[class*=performances_group_container]')
                .find('[class*=performance][class*=available]')
                .first()
                .click(); // this action will change the parent URL
            cy.get('[id=page_selection_eventSeat]').should('be.visible'); // this one will not  be available
        });

Thanks for your help.

Hey, I haven't tried but maybe you can use the switch command twice :

cy.get('[id=page-RateSelection] iframe').switchToFrame(() => {
    cy.get('[class*=performances_group_container]')
        .find('[class*=performance][class*=available]')
        .first()
        .click(); // this action will change the parent URL
});

cy.get('[id=page-RateSelection] iframe').switchToFrame(() => {
    cy.get('[id=page_selection_eventSeat]').should('be.visible'); // hope this one will be available
});

@donaldpipowitch
Copy link

Thank you very much for sharing this snippet. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment