Upcoming feature: automatic Selenium API version matching in Testcontainers
A key design goal for the Selenium support in Testcontainers is to prevent the sporadic breakage of the compatibility between Selenium library JARs and the browsers they interface with. In the past, I’ve seen countless examples of nightmares when projects’ Selenium library dependencies don’t match the browsers that developers have installed locally. This trend became worse when browsers started receiving automatic updates.
The first releases of Testcontainers took an alternative approach: use tagged docker containers for web browsers to lock browser versions to be compatible with a specific version of the Selenium API - namely v2.45.0. This leverages the excellent SeleniumHQ docker-selenium project’s set of docker containers. This means that any project using Testcontainers simply needed to match its selenium-*.jar
dependency versions to version 2.45.0, and compatibility would be assured.
However, I was conscious that this approach would become painful as newer, better versions of Selenium are released. Keeping all users of Testcontainers perpetually locked to using an old version of Selenium doesn’t seem like a developer-friendly thing to do.
Options for change #
Option 1 would have been the simplest thing for me to do: allow users to specify which version of the docker-selenium containers to use. For example,
this in the POM:
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-remote-driver</artifactId>
<version>2.52.0</version>
</dependency>
would need to be paired with this in the JUnit tests:
@Rule
public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer()
.withDesiredCapabilities(DesiredCapabilities.chrome())
.withSeleniumVersion("2.52.0"); // don't try this!
However, declaring a version in two places in this manner is not DRY and quite ugly - not only are we forcing extra boilerplate on the developer, but it needs to be maintained and kept in sync.
Is there an alternative way, where the version would only need to be specified in a single place?
It turns out there is a pretty neat Option 2: determine the project’s version of Selenium at run time. I found that the selenium-api
library is packaged with an incredibly useful piece of metadata in its META-INF/MANIFEST.MF
file:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: linman
Created-By: Apache Maven 3.1.1
Build-Jdk: 1.8.0_72
Name: Build-Info
Selenium-Build-Time: 2016-02-11 11:22:43
Selenium-Version: 2.52.0 <----------- AHA!
Selenium-Revision: 4c2593cfc3689a7fcd7be52549167e5ccc93ad28
The crucial Selenium-Version
attribute allows us to select tagged versions of Chrome and Firefox that were tested for compatibility with this specific version of Selenium.
Manifests are easily overlooked, but occasionally allow a small gem of meta functionality such as this. It’s great that the Selenium developers were thoughtful enough to include this kind of metadata.
Outcome #
The approach that I’ve taken (which should be in v1.0.3 of Testcontainers) is:
- Iterate over all manifests on the classpath (this could be made faster, but it’s simple and it works)
- Locate instances in the
Selenium-Version
attribute - If the attribute was found, use that as the version tag for browser containers. Note that if multiple different attributes were found, it means the project has multiple versions of
selenium-api
on its classpath - a problem which is worthy of a WARN level log message, at least. - This selection code will fall back to a predictable version of Selenium (2.45.0) if, for any reason, it’s unable to determine the API version. However, hopefully this is unlikely - if
selenium-api
is not on the classpath, the project probably has wider issues…
Conclusion #
I’m pretty happy with this approach; it resolves a lingering issue that I’d been meaning to sort out. This solution seems better than the other
Additionally, it aligns with my belief that library authors should go the extra mile to achieve a simple API for their users (developers) - even if it means a bit of extra work is needed on the internals. Many of the design decisions I’ve taken with Testcontainers so far are based on this belief, and I’m happy to be able to include this kind of feature.
What do you think? Please comment on the PR or drop me an email!