【问题标题】:How can I assert that a HTML element does not change its appearance after a stylesheet has been included?如何断言 HTML 元素在包含样式表后不会改变其外观?
【发布时间】:2020-02-08 18:33:08
【问题描述】:

设置

我正在为网站编写插件。它将通过插件的 CSS 向 DOM 添加元素。我希望样式仅限于插件,即一旦插件包含在网页上,插件之外的任何元素都不应改变其外观。

我正在使用 cypress 运行集成测试。当插件包含在页面上时,如何断言所有预先存在的元素的样式保持不变?我可以在插件加载前后访问该页面。

方法

这是我认为应该起作用的:

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
    });
}

问题

CSSStyleDeclaration 中的数字键

expect(newStyle).to.equal(oldStyles[i]) 行失败,因为oldStyles[i] 包含仅列出属性名称的数字键。例如,

// 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"              //+
...
}

解决方法

我通过手动循环遍历 CSS 键并检查键是否为数字来解决此问题。但是,这些数字键只出现在oldStyles 中,而不出现在newStyles 中。我写这篇文章是因为这对我来说看起来很可疑,并且我认为错误可能已经存在。

// 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]);
        }
    }
});

空属性值

我在这里做了一个隐含的假设,即 DOM 已实际加载并已应用样式。据我了解,getLinkListStylescy.get 的调用应该安排在cy.visit 等待窗口触发load 事件之后运行。

来自Cypress documentation

cy.visit() 在远程页面触发其load 事件时解析。

但是,使用上述解决方法后,oldStyles 中的 CSS 规则将得到一个空字符串。例如:

//oldStyles[i] for some i
{
    cssText: "animation-delay: ; animation-direction: ; animation-duration: ; […more]"
    length: 0
    parentRule: null
    cssFloat: ""
    alignContent: ""
...
}

尝试的解决方案

请注意,当我明确使用带有cy.visit 的回调时,此行为不会改变,即:

cy.visit(Cypress.env('theURL')).then(()=>{
    getStyles().then((oldStyles) => {
        // (rest as above)

cy.wait(15000) 也不在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) => {
...

【问题讨论】:

  • 我对赛普拉斯一无所知,但关于这个问题:你最好使用Number.isNaN 而不是isNaN 来检查数字索引。除了值是否为NaN 之外,它还会检查类型。此外,“未定义”与“空字符串”和“空”有很大不同。您会发现大多数开发人员都喜欢精确的语言:)。
  • 如果你为你的插件使用 id,你可以从那里重置/设置样式到只有 id 和它的孩子。
  • @HereticMonkey 感谢您的两个建议。由于我使用的是 TypeScript,其中 isNaNNumber.isNaN 都要求它们的参数为 number 类型,所以无论如何我将 key 转换为数字,因此这两个函数的行为相同。
  • @G-Cyr 问题不在于如何防止 CSS 泄露,而在于如何验证 CSS 没有泄露。

标签: javascript css cypress getcomputedstyle


【解决方案1】:

我无法回答有关空属性值的问题,解决方法应该不会影响事情。如果我理解正确,您在不使用解决方法时会获得属性值?

数字键

这些几乎肯定是cssText 样式的索引,即内联样式

数字键的数量与cssText 中的条目数量完全相同,并且值与cssText 中的键值对的LHS 匹配。

第二个 getStyles() 缺少数字键

你确定吗?

如果我在没有插件挂载的情况下运行你的代码,我会失败,因为它会比较对象引用,

getStyles().then(oldStyles => {
  // no plugin mounted
  getStyles().then(newStyles => {                
    newStyles.forEach((newStyle, i) =>           
      expect(newStyle).to.equal(oldStyles[i])
    );
 });

但如果我使用.to.deep.equal 它会成功

getStyles().then(oldStyles => {
  // no plugin mounted
  getStyles().then(newStyles => {                
    newStyles.forEach((newStyle, i) =>           
      expect(newStyle).to.deep.equal(oldStyles[i])
    );
 });

getComputedStyle() 返回一个活动对象

MDN Window.getComputedStyle()

返回的样式是一个实时的 CSSStyleDeclaration 对象,它会在元素的样式更改时自动更新。

因此您需要在比较之前克隆结果,即使插件在您比较时更改了某些内容,它们也是相同的。

我建议将JSON/stringify() 应用于结果并比较字符串,它非常快,也无需深度相等。

function getStyles() {
  return cy.get('.el-on-the-page *').then((elements) => {
    const styles = []
    elements.each((_, el) => {
      styles.push(window.getComputedStyle(el));
    });
    return JSON.stringify(styles);    
  });
}

getStyles().then(oldStyles => {         
  mountPlugin();       
  getStyles().then(newStyles => {         
    expect(newStyles).to.equal(oldStyles);
  });
});

【讨论】:

  • 无论如何,我最终还是对计算的样式进行了序列化和反序列化,因为 Cypress 或 JS 似乎都无法处理那么长 (20MB) 的字符串。赛普拉斯只是在expect(newStylesJSON).to.equal(oldStylesJSON) 上冻结。
猜你喜欢
  • 2019-09-29
  • 2016-05-21
  • 2020-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-20
  • 1970-01-01
相关资源
最近更新 更多