CSS specificity testing

Viewed 758

Are there any good tools or methods for automatic testing css selectors?

I'm developing a SCSS framework and would like to include automated tests in it. Specifically I would like to have tests to ensure that the css selectors are working properly.

Say for instance that I have the html:

<input class="btn" disabled id="test"></input>

and css

.btn {
 color: red;
 ...
}

.btn:disabled {
 color: green;
 ...
}

I would like to have a test that ensures that the element above with id=test, have the .btn:disabled as the css class with highest priority (last class with highest specificity) and .btn as the second highest priority. In other words, I would like to ensure that the .btn:disabled and .btn css style is applied on the element and that styles in .btn:disabled are overwriting the styles in .btn.

I'm thinking of doing this in selenium. Are there any good ways of doing this? I would not like to hard code the css values into the tests.

4 Answers

The method I settled with is to use getComputedStyle to get the style with "highest priority". In the css I add a "tag" to the content property. In jasmine I then check if the desired tag is the computedStyle. (I will extend this in scss so that the content property is set by a mixin if test mode is used and not set in production.) This only makes a unit test for the class of highest priority, but not for the second highest etc.

Below is a tests to illustrate the example (only the first and last should pass).

// specs code
describe("CSS", function() {
  it("Div element of class test should be handled by .test", () => {
    const testdiv = document.getElementById("testdiv")
    m = window.getComputedStyle(testdiv).getPropertyValue("content"); 
   
    expect(m).toEqual('".test"');
  });

 it("Div element of class test should be handled by div", () => {
    const testdiv = document.getElementById("testdiv")
    m = window.getComputedStyle(testdiv).getPropertyValue("content"); 
   
    expect(m).toEqual('"div"');
  });

 it("Div element should be handled by .test", () => {
    const testdiv = document.getElementById("testdiv2")
    m = window.getComputedStyle(testdiv).getPropertyValue("content"); 
   
    expect(m).toEqual('".test"');
  });

 it("Div element of class test should be handled by div", () => {
    const testdiv = document.getElementById("testdiv2")
    m = window.getComputedStyle(testdiv).getPropertyValue("content"); 
   
    expect(m).toEqual('"div"');
  });

});


// load jasmine htmlReporter
(function() {
  var env = jasmine.getEnv();
  env.addReporter(new jasmine.HtmlReporter());
  env.execute();
}());
.test {
    content: '.test';
}

div {
  content: 'div';
}
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script>
<link href="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css" rel="stylesheet"/>
<div class="test" id="testdiv">TestDiv</div>
<div id="testdiv2">TestDiv</div>

If I understand the question correctly, you're basically asking for a test (for your example) like this:

if -> Element.ComputedStyle.color = green

then -> test passed

else -> test failed (you have CSS structure errors)

Obviously, the browser will interpret specificity correctly. So you're really testing if changes you made by adding/overriding CSS caused unintended visual consequences.

That seems pretty manual question to answer since you'll have to decide what each of those correct states is and then maintain the tests. If you go this route, I'd look at something like Backstop.js. Though CSS visual regression testing is REALLY complex so be careful how much you expect from it.


The Manual Way

Could you solve the problem somewhat manually by creating a SCSS variable that's usually transparent? Then as you're adding/changing code add that variable and change the color to something like pink that's really obvious? At this point, you should see where things override when you render the page.

If you're making a CSS framework, I'd test against your documentation since that should show you previous examples that would be overridden.


CSS Blocks

You may also want to look into the CSS Blocks API. It's not going to be a "test" exactly but the API provides scoping and compile errors that might help you catch some of those issues sooner than later.

Here's the pertinent part:

With CSS Blocks new resolution system, cascade conflicts will be caught for you before you even know they exist and you will never have to fight a specificity war ever again.

As you have mentioned you can achieve this with Selenium. In terms of methodology, if you're looking to maintain this long term then I would recommend following the Page Object Model. The official documentation on this is available here, and there are some other articles in various language here, here, and here.

Essentially what it boils down to is create classes or models for the pages (or page sections/components (as in a form that has multiple controls)), this class will then have properties/fields for each of the elements on the page that you want to interact with. The advantages of this approach are:

  • A single place to change if you need to update a selector (maintainability)
  • The underlaying code which can be ugly can be exposed through a nice interface that uses fluent syntax (readability)

How this looks (since you haven't specified a language I'll go with C#:

public class LoginPage
{
    // FindBy attributes should decorate each property to specify the selector
    public WebElement UsernameInput { get; set; }
    public WebElement PasswordInput { get; set; }
    public WebElement LoginButton { get; set; }

    public LoginPage()
    {
        ...
    }

    public LoginPage Load(this LoginPage page)
    {
        // code to navigate to the login page
    }

    public LoginPage EnterCredentials(this LoginPage page, string username, string password)
    {
        // code to fill out inputs
    }

    public HomePage Login(this LoginPage page)
    {
        // code to click login button
    }

    // Other methods
}

How this looks when you use it:

HomePage homePage =
    new LoginPage()
    .Load()
    .EnterCredentials("user", "pass")
    .Login();

// Now you can perform operations on the HomePage

CSS Specificity

As per the documentation Specificity is the logic by which the browser decides which CSS property values are the most relevant to an element incase there are two or more conflicting CSS rules that point to the same element and which will be applied. Specificity is calculated based on the matching rules which are composed as per different CSS selectors.


How to calculate Specificity

There are a couple of rules to calculate Specificity based on points which are as follows:

  • style attribute: 1000
  • id attribute: 100
  • class or pseudo-class: 1

Calculating the CSS Specificity

Let us calculate the Specificity of both the CSS

  • Sample A:

    .btn {
     color: red;
    }
    
    • Explanation: Contains a class i.e. btn. So Specificity is 1.
  • Sample B:

    .btn:disabled {
     color: green;
     ...
    }
    
    • Explanation: Contains a class i.e. btn and a pseudo-class i.e. disabled. So Specificity is 2.

Tests

The CSS specificity can also be visually verified through Specificity Calculator:

Specificity


Conclusion

As the CSS sample B has a greater Specificity, so CSS sample B will be applied to the element:

<input class="btn" disabled id="test"></input>

Outro

However there are some more granular CSS Specificity Rules on:

  • Equal specificity: the latest rule counts.
  • ID selectors have a higher specificity than attribute selectors.
  • Contextual selectors are more specific than a single element selector.
  • A class selector beats any number of element selectors.

You can find a detailed documentation in CSS Specificity

Related