Setting
I’m writing a plugin for a website. It is going to add elements to the DOM that are styled via the plugin’s CSS. I expect the styling to be limited to the plugin, i.e. no elements outside the plugin should change their appearance once the plugin is included on the web page.
I’m running integration tests using cypress. How can I assert that all the pre-existing elements’ styles stay the same when the plugin is included on the page? I have access to the page before and after the plugin has been loaded.
Approach
This is what I thought should work:
cy.visit('theURL');
getStyles().then(oldStyles => { // Get the styles of the elements
mountPlugin(); // Mount the plugin (including CSS)
getStyles().then(newStyles => { // Get the (possibly changed) styles
newStyles.forEach((newStyle, i) => // Compare each element’s style after
expect(newStyle).to.equal(oldStyles[i]) //+ mounting to the state before mounting
);
});
});
function getStyles() {
return cy.get('.el-on-the-page *').then((elements) => { // Get all elements below a certain root
const styles: CSSStyleDeclaration[] = []
elements.each((_, el) => { // Get each element’s style
styles.push(window.getComputedStyle(el)); //+ and put them it an array
});
return styles; // Return the styles
});
}
Problems
Numeric keys in the CSSStyleDeclaration
The line expect(newStyle).to.equal(oldStyles[i])
fails because oldStyles[i]
contains numeric keys that only list property names. For instance,
// oldStyles[i] for some i
{
cssText: "animation-delay: 0s; animation-direction: normal; […more]"
length: 281
parentRule: null
cssFloat: "none"
0: "animation-delay" // <-- These elements only list property names, not values
... //+
280: "line-break" //+
alignContent: "normal" // <-- Only here there are actual property values
... //+
zoom: "1" //+
...
}
Workaround
I fix this by looping through the CSS keys manually and checking if the key is a number. However, these numeric keys only appear in the oldStyles
, not in newStyles
. I’m writing this because this looks fishy to me, and I assume that the error might already be there.
// Instead of newStyles.foreach(…) in the first snippet
newStyles.forEach((newStyle, i) => {
for (const key in newStyle) {
if(isNaN(Number(key))) {
expect(newStyle[key]).to.equal(oldStyles[i][key]);
}
}
});
Empty property values
I’m making the implicit assumption here that the DOM is actually loaded and has applied the styles. From my understanding getLinkListStyles
’s call to cy.get
should be scheduled to run only after cy.visit
has waited for the window to fire the load
event.
From the Cypress documentation:
cy.visit()
resolves when the remote page fires itsload
event.
However, with the above workaround employed, I get an empty string for the CSS rules in oldStyles
. For instance:
//oldStyles[i] for some i
{
cssText: "animation-delay: ; animation-direction: ; animation-duration: ; […more]"
length: 0
parentRule: null
cssFloat: ""
alignContent: ""
...
}
Attempted solutions
Note that this behaviour does not change when I explicitly use a callback with cy.visit
, i.e.:
cy.visit(Cypress.env('theURL')).then(()=>{
getStyles().then((oldStyles) => {
// (rest as above)
Neither does cy.wait(15000)
at the beginning of getStyles()
:
function getStyles() {
cy.wait(15000); // The page has definitely loaded and applied all styles by now
cy.get('.el-on-the-page *').then((elements) => {
...