What I’d like to share with you is my suffering from dealing with browsers. I’m an author of a Clojure library that automates browsers. Briefly, the library doesn’t depend on Selenium since it implements the Webdriver protocol with pure Clojure. It brings flexibility and freedom in the way you’d like to build your API.

At the same time, everybody who is willing to tame several browsers at once is doomed to open the Pandora box. There is the official standard I thought, so definitely everybody will follow it. A naive laddie I was.

I don’t know if there is some kind of curse or black magic, but sometimes developers cannot just implement the standard as it dictates the things should be done. For example, it says the server should accept a value field. Developers write code that takes text instead. When I see it, I have strange feeling of being completely stunned without any understanding of what’s going on here.

But what exactly I wanted to discuss is how does Mozilla develop their software.

Briefly, they break everything apart moving further. And I don’t know how to deal with it. Maybe they enjoy coding in Rust so much that their mission to ship good software has faded away.

I know I’m not a Mozilla engineer and would never be capable of joining them, but still. As a user of their products, namely Geckodriver and Webdriver utilities, I consider myself being right when criticizing them.

With each next release of Geckodriver, it behaves worth and worth. When I first started developing the library (a bit less then 1 year ago), it was 0.13 version available and it worked fine. I wrote a bunch of unit tests that all passed. Nowadays, the latest version is 0.19 and it just doesn’t work.

Yes, you heard correct, it does not even respond to any API call:

~/Downloads> ./geckodriver-0.19.0
1510326430666 geckodriver INFO geckodriver 0.19.0
1510326430680 geckodriver INFO Listening on 127.0.0.1:4444

curl -X POST -d '{"desiredCapabilities": {}}' "http://127.0.0.1:4444/session"

Calling curl utility throws a long stack trace saying something about sandbox restrictions without any word on how to fix that or at least where to look for help.

Here is a related issue that confirms I’m not the first one who faced it. It’s closed! “As I said, this isn’t a problem with geckodriver”, one of developers says. OK, but I’m still curious about why does the version 0.13 work fine whereas switching on 0.19 leads to failure? Proof:

~/Downloads> ./geckodriver-0.13.0 --version
geckodriver 0.13.0

~/Downloads> ./geckodriver-0.13.0
1510327215587 geckodriver INFO Listening on 127.0.0.1:4444

curl -X POST -d '{"desiredCapabilities": {}}' "http://127.0.0.1:4444/session"
>>> {"sessionId":"e744bbdd-1b3f-9249-827f-02204bbc81c8","value":{"acceptInsecureCerts":...

Ok, let’s decrease the version a bit. I downloaded them moving backwards in time. Again, 0.18 still does not work. We need to go deeper. The previous one numbered with 0.17 starts well, but suddenly, I cannot fetch a new session with my library, it just returns nil value. That’s strange.

Going down to 0.15. Don’t know why, but I cannot fill any input field, the API fails with a strange error saying I didn’t pass the “text” field. Hm, I clearly remember that the API accepts “value” field. That’s what the W3C standard says. Geckodriver 0.13 follows it, but not the 0.15 release! After fetching the official Mozilla repo and searching in its history for a while, I found this (truncated):

diff -r 225d1faf513b -r b429bf0078c4 testing/webdriver/src/command.rs
--- a/testing/webdriver/src/command.rs	Tue Mar 07 18:50:56 2017 +0000
+++ b/testing/webdriver/src/command.rs	Wed Mar 15 13:54:19 2017 +0000
@@ -752,7 +752,7 @@

 #[derive(PartialEq)]
 pub struct SendKeysParameters {
-    pub value: Vec<char>
+    pub text: String
 }

 impl Parameters for SendKeysParameters {
@@ -760,26 +760,14 @@
         let data = try_opt!(body.as_object(),
                             ErrorStatus::InvalidArgument,
...
-            Ok(chars[0])
-        }).collect::<Result<Vec<_>, _>>());
+        let text = try_opt!(try_opt!(data.get("text"),
...
+            text: text.into()
         })
     }
 }
@@ -787,10 +775,7 @@
 impl ToJson for SendKeysParameters {
     fn to_json(&self) -> Json {
         let mut data = BTreeMap::new();
-        let value_string: Vec<String> = self.value.iter().map(|x| {
-            x.to_string()
-        }).collect();
-        data.insert("value".to_string(), value_string.to_json());
+        data.insert("value".to_string(), self.text.to_json());
         Json::Object(data)
     }
 }

Hold on, what was the purpose to change that field? Everything was working, right? Who asked for that? Did the standard change? No, it’s still the same! Why have you guys just changed the API? You could easily rewrite the code without renaming “value” field into “text”.

Listen, I’ve already got three different versions of those API for Chrome, Firefox and Safari. In addition to that, your force me to switch on version inside Firefox branch turning my code into if/else hell.

From your perspectives, what’s the difference in processing either “value” or “text” field? But for me, you’ve broken my software! It doesn’t work anymore.

I could not find a diff that would proof the fact of moving “sessionId” field because the repo’s history is pretty complicated. But it’s easy to check that. The version 0.17 returns:

{
  "sessionId":"4decec3e-e564-b349-bb41-b27b5f307e01",
  "value":{
    "acceptInsecureCerts":false,
    "browserName":"firefox",
    "browserVersion":"52.0
    ...

whereas 0.13 returns:

{"value":{
  "sessionId":"d0dc41f8-9c17-934e-be2b-708c72f0fb9c",
  "capabilities":{
    "acceptInsecureCerts":false,
    "browserName":"firefox",
    ...

Look, they just wrapped the whole data into “value” subfield shifting it one level below. Again, what was the purpose of doing this? Did you think of those users who have already been using your code for some time?

Thanks to those weird changes, I need to refactor my code just because Mozilla guys decided to rename something. And more over that, thank you for not sending back the driver’s version in API responses. It would be not so challenging when having it.

I wonder why has Chromedriver been working all that time without errors? It’s written in C++ and Python and the code is quite simple and linear. And it’s much stable then its Rust-driven rival.

OK, it’s time to summarize all together. I don’t think that anybody will share my misery on the subject. The Webdriver spec is quite specific thing, so who would really care…

But the main point of that talk is you should never break backward capability even at the gunpoint. Just keep things working, that’s the main priority when you change something. Extend, but not truncate your product. And really, just use it sometimes in a way that an ordinary user does.