Creating Testable, Modularized Chrome Extensions

When creating software, it’s very important to be able to verify the correctness of your code, and writing a Chrome extension is no exception. For an automated testing suite to work, we require two things. First, the code must be modularized in a way that allows for simple unit tests to be written. If this isn’t the case, writing tests can become a very tedious and messy process. Second, the testing suite needs to be able to access and interact with the application as a whole (integration tests). At first glance, it appears Google Chrome’s extensions satisfy neither of these requirements. However, with a bit of creativity, we can find solutions to both.

How Chrome Makes Testing Hard

We’ll begin by looking at the first requirement: The code must be modularized in a way that allows for simple unit tests to be written. Chrome extensions are broken into three parts. The popup (the window that shows when you click the extension icon), the background script (any extension logic that runs in the background), and the content script (any extension logic that runs as a part of the current web page). Each of these must be exactly one file.

This means if your extension has a lot of complex functionality, you end up with a massive background script and popup script. I took a look at the Google Keep extension and found their popup script is 8,500 lines, and their background script is 26,000 lines! Trying to find a bug in a 26,000-line Javascript file sounds like a great way to ruin your Monday. As expected, this also makes unit tests challenging.

We’ll discuss a few solutions soon, including how Google likely manages their 26,000-line background script, but for now let’s move on to the second requirement: The testing suite needs to be able to access and interact with the application as a whole. The good news is that Google provides ChromeDriver, a way of programmatically interacting with Chrome. The bad news is that they seem to have forgotten about programmatically interacting with extensions.

As of today, there isn’t a way to click on your extension’s icon and open the popup using ChromeDriver. The workaround is to navigate to http://(your extension id)/popup.html, and view your popup there. This workaround is flawed, however. It renders the popup in a differently sized window than your normal popup, which makes asserting on UI behavior challenging or impossible depending on what you want to do. Also, if you want to assert that interacting with your popup does something to a page that’s currently open, you’re out of luck, since you need to navigate your browser to the popup to be able to interact with it.

How We Can Test Chrome Extensions

Creating a Chrome extension that’s testable is possible, but we need to get a bit creative. To begin, we need a way to modularize our program into bite-sized chunks that each have a specific purpose. Then, we need to figure out a solution to our integration test issues, including setting up some sort of headless browser that can run tests. Finally, we need to actually write tests.

Modularization

There are two different solutions to modularization. The first is to use a background HTML page instead of a background script. This HTML page’s role is to simply “require” a bunch of different Javascript files in order of dependency using <script> tags. For example, this is what Microsoft does in their OneNote extension. This configuration allows you to modularize your scripts in separate files and then unit test each individually.

  

The other option is to use some sort of build system. By using a build system (such as Gulp), you can develop the extension in any form you’d like, then allow the build system to concatenate files together, thus creating Chrome’s required single script at build-time. By using a task runner like Gulp, you could have a setup where Gulp runs unit tests on your modules, then minifies and compiles everything into Chrome’s required format, and finally runs integration tests on the bundled package. This solution requires Node as a development dependency, but it offers more flexibility than the background.html option.

Integration Tests

Writing functional integration tests is a little harder. Assuming we want to use some sort of CI system, we’ll probably want our integration tests running in a headless browser, so we should first focus on trying to get a headless browser running with our Chrome extension.

Selenium is a popular choice for browser automation, and has wrappers in just about every major language. It also works with ChromeDriver, which is great news, since we can’t load our Chrome extension into any other kind of browser. Depending on your wrapper, you’ll either be given a method to load in an extension or you’ll have to use the driver flag --load-extension=extension_path. If you’re running the browser in headless mode, it may also be required to pass --no-sandbox and --disable-impl-side-painting.

The extension path you give the driver should be the path to the folder containing your manifest.json, and it should not be zipped. If either of these aren’t true, Chrome crashes. Once you’ve gotten Selenium to navigate to your extension popup at http://(your extension id)/popup.html, you’re ready to put together some tests.

Actually Writing Tests

At this point, writing tests is fairly simple. We can use Karma and Jasmine to test our Javascript modules in the same way that we could normally use Karma and Jasmine. For integration tests, I chose to use RSpec paired with Watir, a Ruby Selenium wrapper, but if you want to keep the entire testing suite running in Node, WebDriver.io + Jasmine is a great option. We can use these frameworks like we normally would, now that our project is setup correctly.

A Working Example

I’ve put together a working, testable example implementing everything mentioned in this blog post. It can be found on GitHub here. This can be used either as a proof of concept or as a base for anyone writing a Chrome extension. The extension has a super simple background module that we test with a unit test, and some text on a popup that we test using an integration test. Unit tests are done using Karma and Jasmine. Integration tests are done using Watir and RSpec. Hopefully this example helps people write better, more testable Chrome extensions in the future.

Questions? Have a better method for testing Chrome extensions? We’d love to hear from you. Feel free to shoot us an email at contact@tinfoilsecurity.com with any awesome browser extensions you’re building and how you’ve gone about testing them.


Nathaniel Woodthorpe

Nathaniel is a Software Engineering Intern at Tinfoil. He's currently studying at the University of Waterloo and working on finishing his degree in Computer Science. He got into programming writing bots for online games back in elementary school. When he isn't coding, he's usually outside climbing something.

Tinfoil Security Blog

Tinfoil Security provides the simplest security solution. With Tinfoil Security, your site is routinely monitored and checked for vulnerabilities using a scanner that's constantly updated. Using the same techniques as malicious hackers, we systematically test all the access points, instantly notifying you when there's a threat and giving you step-by-step instructions, tailored to your software stack, to eliminate it. You have a lot to manage; let us manage your website's security.