Chrome 91 might break your tests

Here are 5 ways to keep them running

Jan Molak
Jan Molak

--

If you’re amongst millions of developers who rely on Chrome and Selenium WebDriver version 3.x to run your tests, you might have noticed that with ChromeDriver 91, your tests have started (or will soon start) to behave in a rather strange way.

In particular, instead of returning the value of an attribute, WebElement.getAttribute(attributeName) method always returns null.

This can be rather inconvenient, as many tests rely on this API to check the state of input fields, verify CSS classes of a WebElement, and so on.

So what’s going on, and what can you do about it?

Let’s start with the root cause.

The issue

The issue was first reported in the Chromium bug tracker on May 3rd 2021.

The Chromium team believes that a recent change to the attribute endpoint has negatively impacted the behaviour of the GetElementAttribute command, which now leads to WebElement.getAttribute returning null instead of the actual value of the attribute.

The good news is that a fix has been proposed on June 3rd, and while it’s not available at press time, it’s likely to be available soon. 🤞

So what can you do while waiting for the fix?

Just like with any other problem in computer science, there are several possible solutions and workarounds.

Possible solution #1

The first option is for you to avoid using Chrome / ChromeDriver 91 with Selenium Webdriver 3.x until the fix is available and instead rely on an older version, i.e. Chrome 90.

Of course, this option is available only if you can accept the risk of not testing your software with the latest Chrome for a while, and you’re in full control of the runtime dependencies of your test suite.

If you’re using a cloud-based testing platform, configuring it to use an older Chrome is rather straightforward. To do that, configure WebDriver’s “desired capabilities” as per your provider’s instructions:

Possible solution #2

The second option could be for you to ensure that your tests run in W3C standards-compliant mode by setting goog:chromeOptions.w3c:true since the issue affects only the legacy JsonWireProtocol endpoint.

To do this in JavaScript, you’ll need to set the w3c flag as follows:

capabilities: {
'browserName': 'chrome',
'goog:chromeOptions': {
w3c: true,
args: [
'--headless',
]
},
}

While W3C mode has been, in fact, the default since Chrome 75, there’s an important caveat here.

Selenium WebDriver 3.x doesn’t fully support W3C standards-compliant mode, so enabling it might lead to undesired side-effects such as the deprecatedbrowser.actions API not working due to incompatibility.

If your tests don’t use any deprecated APIs, you might be fine, though.

Possible solution #3

The third option could be to upgrade your test suite to use Selenium WebDriver 4.x (currently in beta), enable W3C compatibility mode, and ensure that your tests don’t use any deprecated APIs.

If you don’t mind relying on a tool that’s still in beta and you can migrate away from using deprecated APIs, this option is quite tempting.

Possible solution #4

The fourth option could be to implement a programmatic fallback in scenarios where WebElement.getAttribute returns null and retrieve the attribute value using JavaScript injected via browser.executeScript API.

With this approach, your test code would work with both Selenium 3.x and 4.x, as well as both newer and older versions of Chrome.

In JavaScript, the code to do that would look more or less like this:

const attribute = await element.getAttribute(name).then(value => {
if (value !== null) {
return value;
}

return browser.executeScript(`
function getAttribute(webElement, attributeName) {
return webElement.getAttribute(attributeName);
}
`, element, name);
})

This is a decent workaround and, in fact, something similar to what Serenity/JS acceptance testing framework uses under the hood.

Of course, this approach is viable only if your test suite correctly separates business logic from implementation detail and is kept DRY, so that you don’t have to copy and paste the above snippet in dozens of test scenarios.

Possible solution #5

As you might have expected, the last and easiest solution would be for you to avoid calling low-level Selenium WebDriver APIs like getAttribute altogether. Instead, you could rely on a higher-level framework like Serenity/JS to take care of it for you.

Thanks to implementing the Screenplay Pattern, Serenity/JS makes abstracting low-level WebDriver APIs (as well as REST testing APIs and many more) super easy.

For example, to retrieve a value of an HTML attribute with Serenity/JS you’d use a “question” aptly named "Attribute”:

import { Attribute } from '@serenity-js/protractor';Attribute.of(element).called(attributeName)

So if you had a checkbox widget like this:

<input type="checkbox" id="confirm" />

You could tell your Serenity/JS Actor to verify whether it’s been checked or not as follows:

import { actorCalled } from '@serenity-js/core';
import { Ensure, equals } from '@serenity-js/assertions';
import { Target } from '@serenity-js/protractor';
import { by } from 'protractor';
const confirmation = Target.the('confirmation checkbox')
.located(by.id('confirm'));
actorCalled('Alice').attemptsTo(
Ensure.that(
Attribute.of(confirmation).called('checked'),
equals('true')
),
);

And that’s it!

Under the hood, Serenity/JS will pick whatever method of retrieving the element attribute is most appropriate so that you don’t have to worry about it. In particular, Serenity/JS will try to use WebElement.getAttribute and if that doesn’t work — fall back to retrieving the value via injected JavaScript as I showed you earlier.

This feature is available as of Serenity/JS 2.29.0

Getting started with Serenity/JS

To get started with Serenity/JS, check out the demo below, then grab one of the boilerplate projects available on GitHub and start hacking!

Please also note that while the demo below uses Angular Protractor, I’ve designed Serenity/JS to support most automation tools, with support for WebDriverIO already under development! 🎉

To learn more about Serenity/JS, check out the website: serenity-js.org

For project news, updates, and upcoming courses — follow @Serenity/JS and @JanMolak on Twitter.

If you have any questions about the framework — join our community chat and our friendly community will help you out!

I’m curious, which solution did you go for? Is there any solution I missed?

Let me know in the comments! 👇👇👇

--

--

Consulting software engineer and trainer specialising in enhancing team collaboration and optimising software development processes for global organisations.