sinon-chrome
sinon-chrome copied to clipboard
Please document how to use browser / webextension API in tests
Here's a reduced test case: https://gist.github.com/arantius/33d318f57ced2f1b7ef5c49740b00e01
Try to use sinon-chrome, with browser
API. Fail. chrome
here works (well, doesn't crash before Karma initialization does, for this reduced case). Documentation claims both APIs are supported, but I can't tell how.
I've been looking into this, and this is what I've found.
Firstly, loading sinon-chrome
as a framework requires the karma-sinon-chrome
package. However, this package requires an older version of sinon-chrome
and therefore downloads and uses that version (in it's own local node_modules namespace).
Unfortunately, that's only part of the problem. Suppose you manually load the WebExtension part of sinon-chrome
in the files section of the karma.conf[1] you will only have access to a global 'chrome' object. Despite the name, this is the actual 'browser' API[2]. The reason that that the global object name is 'chrome' is that the auto-bundler (webpack) is configured to name the object 'chrome'.
What makes this even worse is that, while the APIs are 'defined', the stubs that are created are not configured to return promises. Therefore any time you perform a .then
the interpreter will error with browser.func() is undefined
(since nothing is returned).
Although, creating the stubs to autoresolve a promise would probably create all sorts of problems too. Since the promise chain would be expecting particular input and I suppose it can only be simulated to a certain extent.
Along those lines, the best thing I could offer is to assume, during testing, that all browser
functions are working properly (heh) and use before()
blocks to configure what the functions resolve to.
[1] With say, ./node_modules/sinon-chrome/bundle/sinon-chrome-webextensions.min.js
.
[2] As defined in https://github.com/acvetkov/sinon-chrome/blob/058cc9304fedfb3db0e04eae7ff0bdbe93dfdda2/src/config/stable-api-ff.json
It should also be noted that the Karma config linked will uses the actual browser to execute the tests. This particular package was created to run tests in a node environment. Thus the expectation would be to do the following:
const chrome = require('sinon-chrome/extensions');
const browser = require('sinon-chrome/webextensions');
You could use something like requireJS to mimic this functionality. I attempted to do this with the Greasemonkey codebase, unfortunately due to async loading (of requireJS) and the fact that the GM codebase isn't based on a module / export structure I was unable get it working properly.
@arantius hi. your problem is related to karma-sinon-chrome
, not sinon-chrome
.
karma-sinon-chrome
uses old dependency.
@Sxderp you are great!
Thanks for the confirmation of that. I'm working on a PR ( https://github.com/9joneg/karma-sinon-chrome/pull/5 ) with them to get their internal dependency updated.
So I've updated the originally linked example, to use my updated branch. I still get the same ReferenceError: browser is not defined
. Does the above ("... the global object name is 'chrome' is that the auto-bundler (webpack) is configured to name the object 'chrome'.") imply that I must have set up code to alias chrome
to browser
to make this work?
Does the above ... imply that I must have set up code to alias
chrome
tobrowser
to make this work?
Just to be completely clear I want to take a step back. Some of the following you might already know.
Node packages are created in such a way that all public facing APIs must be assigned to module.exports
where module
is a global reference to the current 'package' that Node handles itself. This package, sinon-chrome
uses the native export default
syntax. Which is slightly different but for our purposes it can be the same. Therefore, in this package, the following line in src/extensions/index.js
is functionally equivalent to the second.
export default new Api(config).create();
module.exports = new Api(config).create();
Now, in order to use the module, in your main code (in a Node environment) you would do:
const chrome = require('sinon-chrome/extensions');
Which is basically the same as (not valid code, don't do the below).
const chrome = module.exports = new Api(config).create();
In order to provide this package in a non-Node environment a script (webpack) is run. What this does is bundle all the necessary files into a single file. This can be represented with a single line of code (not valid code, but demonstrates what's happening).
// For src/extensions
window.chrome = require('sinon-chrome/extensions')
Furthermore, the bundler does the following when it creates the bundled src/webextensions
module.
window.chrome = require('sinon-chrome/webextensions')
So yes, if you include the WebExtension bundled file sinon-chrome-webextensions.min.js
you need to create an alias from chrome
to browser
. And this shouldn't cause any problems since the FF ecosystem uses the same endpoint names for both namespaces (as far as I'm aware).
Looking at the patch you submitted, the update would only give access to the endpoints defined for Chrome extensions: here. Since Chrome and FF use slightly different endpoints aliasing chrome
to browser
would not be a solution, assuming you continue using karma-sinon-chrome.
The simplest solution is to just include the the endpoints you want, in this case FF, in your list of Karma files and then alias chrome
to browser
.
Alternatively, although much more work you could modify the code your testing (in this case Greasemonkey) to use a module.exports structure with either requireJS or natively (I don't think FF supports this). Then you would be able to test your code directly in Node without having to serve the files like Karma does.
Probably not the best idea.
One way to test completely in Node without exporting the system under test using module.exports is to assign browser
on global
, then require
the production code and finally wait until process.nextTick
. Here's an example how to do so.
Another way would be to have something like if (process && module && module.exports) { module.exports = foo; }
in the system under test which would allow to export/require the actual code without having to use e.g. requireJS. Or setting global._testEnv = true
in the test and doing if (_testEnv) { module.exports = foo; }
in the production code. Admittedly those are not the cleanest solutions, but personally I think it's a small price to pay for easy to test code.