- Battery status web app
- Simple test
- Mocking
navigator.battery
property - Mocking
navigator.getBattery
method - Battery status updates
- No battery API
- See also
Battery status web app
In source repo bahmutov/demo-battery-api there is a web application forked from pazguille/demo-battery-api that uses navigator
browser API to show the current battery status. You can try the demo of the application at http://pazguille.github.io/demo-battery-api/. It should look something like this:
You can see the application JavaScript code in src/index.js. The main piece of code tries to grab battery status using either navigator.battery
or navigator.getBattery
properties.
1 | if (navigator.battery) { |
Let's test this code a little bit to make sure it works as expected.
Simple test
Since we do not know anything about the computer running the end-to-end test, our first Cypress spec simple.js is pretty bare.
1 | /// <reference types="Cypress" /> |
We can only check if the battery percentage element is visible - but not what is shows, because we don't know what value to expect there.
It would be much better to make the test deterministic. For example, we could mock the navigator.battery
property the application code checks first. The test can now check each displayed value to be properly rendered, as battery.js spec demonstrates.
1 | /// <reference types="Cypress" /> |
This end-to-end test covers a larger surface of our application's user interface.
The previous test has confirmed that navigator.battery
property is read by the application code and the values are rendered correctly. What about the case when the navigator.battery
is unavailable and the application falls back to navigator.getBattery
method to read the current energy status? Let's write a test in get-battery.js to confirm that it works too.
1 | /// <reference types="Cypress" /> |
The DOM shows the mocked property values correctly.
Any time we mock an existing application method, I prefer to create a Cypress method stub. The stub allows us to confirm that it was actually called (and with the right arguments if required). The second test in spec file get-battery.js confirms the DOM elements and that the navigator.getBattery
was actually invoked.
1 | it('calls navigator.getBattery', function () { |
Battery status updates
In the mocks above, we returned a no-op addEventListener
method with the battery object. Let's return something meaningful and verify our application updates itself when a new battery event is emitted. The application attaches twice to the returned battery object in order to listen to the following events:
1 | window.onload = function () { |
The test is a little bit longer now - I have included a lot of comments and additional steps to make the steps clear. In essence, we set the initial battery status, collect the functions passed by the application, verify them, then call them and verify that the DOM shows the changed values. The spec file updates.js contains this test.
1 | /// <reference types="Cypress" /> |
No battery API
Great, our application can use either navigator.battery
property or navigator.getBattery
method to show the initial battery charge status and listen for updates. But what if the browser does not have this API at all? The browser support for this API is really limited to Chrome only.
Let us delete both navigator.battery
and navigator.getBattery
properties before running out test in no-battery.js spec. The test below shows that deleting navigator.getBattery
does not work. The method remains there!
Update: you can directly delete the battery method from the navigator
object - you just need to delete it from the prototype object! See "Cypress Tips & Tricks" Disable ServiceWorker section.
1 |
|
Here is a trick - instead of running delete navigator.getBattery
overwrite it using Object.defineProperty
like this:
1 | cy.visit('/', { |
And it will stay undefined - and the test will fail because the application crashes 💥
1 | it('should not crash', () => { |
The source of the crash is easy to find - the code calls battery.addEventListener
without checking first if the battery
is defined.
How do we prevent crashes like this from happening? How can we better target our tests to uncover all the edge cases? Do we need to wait for Cypress cross-browser support and run the same test in Firefox to discover the bug? Or do we randomly delete browser API methods hoping to emulate a real-world scenario?
I will show how we can collect code coverage during end-to-end tests and "discover" the missing code paths in our tests. But it will be a different blog post, so stay tuned by following @bahmutov on Twitter, or subscribe to this blog's RSS feed.
See also
- Cypress stubs and spies are sinon.js stubs, so it makes sense to get familiar with this wonderful library.
- Cypress guide to spies, stubs and clocks is really good.
- Update: read the new blog post "Code Coverage for End-to-end Tests" that explains how to collect code coverage during end-to-end tests and fix the missed edge case.