jestjs / jest

Delightful JavaScript Testing.
https://jestjs.io
MIT License
44.33k stars 6.47k forks source link

Targeting more than JSDOM #848

Open nhunzaker opened 8 years ago

nhunzaker commented 8 years ago

Regarding https://github.com/facebook/react/issues/5703, I wanted to open up conversation about the possibility of supporting JavaScript environments beyond JSDOM.

What does this look like?

Does it involve leaning on tools such as Karma, browser-run, or zuul?

Could it be as simple as providing a way to package a fully-encapsulated test as an environment-agnostic payload?

In any case. I thought it was worth starting discussion here.

cpojer commented 8 years ago

Thanks for opening this issue. I'll triage some older issues that are related to this task and close them in favor of this fresh discussion. Browser testing is part of my plan for Jest, however it isn't something we are planning to use at FB so it doesn't have a high priority. This is something where I'd be excited to support someone from the community – this is certainly a challenging task but it would make Jest even more compelling to people :)

browser-run looks really awesome. If you look at the jsdom environment: https://github.com/facebook/jest/blob/b01ab18e0c703a715302a6959e3fca9e6b0260cf/src/environments/JSDOMEnvironment.js#L36 Jest already has a way for environments to run code in any way they want.

This would be easy if it wasn't for the fact that this callback would be async but Jest relies on synchronous calls to require to load the next module. Most of this is done in https://github.com/facebook/jest/blob/master/src/HasteModuleLoader/HasteModuleLoader.js (soon to be renamed to Runtime or similar). What's interesting however is that almost nothing in the module loader is entirely node specific and it could be packaged up and run in a browser.

At that point, here is what we'll need to do:

nhunzaker commented 8 years ago

As a community member, is there a component here that would be best for me to work towards? I'm still familiarizing myself with Jest internals, but it seems like the bulk of the work has to be done in HasteModuleLoader.

One of the attractive parts of Karma is how easy it is to integrate browserify and webpack. It is not difficult to test code bundled in the same way between test and dev. This is attractive to me. While not a direct concern to this issue, I am curious if Jest could integrate with these tools.

This is a difficult problem. Looking forward to digging in.

cpojer commented 8 years ago

I think we'll first need to think pretty hard on how to implement this in a way that it's gonna work well before we start writing any code. Once we have a good idea of what we are gonna build here, I can think more about the staging and smaller chunks of work.

I think one of the big questions is how to create the bundle. webpack/browserify will be insufficient for a few reasons:

I'm all for integrating with existing tools, but it seems that we need the ability to let Jest do it's runtime work of sandboxing and shipping its own require implementation.

Another option would be to make the runtime parts of Jest work in the browser and consider them as entirely separate from the cli runner. One example is the modularization we are doing already: jest-mock (https://github.com/facebook/jest/tree/master/packages/jest-mock) can already be used in any environment. The bundling would then be up to the developer (just get the JS to the client, and put it somewhere that Jest can execute it). Instead of jest-cli, it would be jest-browser and it would be a separate framework that shares the same core and could potentially run the same tests but with vastly different configurations and setup. I'm not sure if that's a good idea.

jquense commented 8 years ago

been thinking about this a bit. Mocking is one of the big features of Jest over other options., this in particular makes a browser runner hard since the execution environment really needs to understand modules. to that end I think using webpack, under the hood, makes a lot of sense.

webpack can more than handle the resolution and mocking concerns, with the right set of plugins (which can consume jest-resolve). the suggestion tho, is to use it as an implementation detail/node environment polyfill.

i need to spend more time consolidating my thoughts (just typing this up quickly on my phone) but I think the big bits that would be needed in jest is pluggable environments (jsdom, browser) and an environment agnostic runtime/runner. I suppose similar to karma s launchers but maybe less generic

nhunzaker commented 7 years ago

@jquense I don't want to run the risk of repeating you too much but I wanted to share some thoughts too.

I think the big bits that would be needed in jest is pluggable environments (jsdom, browser) and an environment agnostic runtime/runner

I wonder what the minimum required surface area would be for browser execution. What happens when --env (jsdom|node) gets called? What code path activates differently?

Could mocking be handled within node, leaning on existing work, with most code execution happening through passing a bundle of javascript to a browser? Could this be as simple as preparing a bundled javascript file, injecting that into a browser context, and reporting back results?

I think this is how Karma's test harness works. I gravitate towards keeping this as simple as dumping JS on the page and tracking console.log/window.onerror. Though I'm sure it's more complicated than that.

jquense commented 7 years ago

karma requires a preprocessor for any sort of modular code, such as karma-webpack.

Could mocking be handled within node, leaning on existing work, with most code execution happening through passing a bundle of javascript to a browser?

this is what im suggesting, but the above would require building a file concatenator based on module/depedency graphs with custom resolution logic. essentially webpack :P i was thinking we could save a ton of hard work by leaveraging webpack instead of builing something. the rest is figuring out api boundries between the test runner, environment and test providers

jquense commented 7 years ago

I think that there is a big difference between the feature set of something like Jest vs karma. karma is a simply a pluggable runner/framework for running a set of tests in many different environments. Jest on the other hand makes a lot of assumptions about the environment. Jest has wider scope than karma; its more like karma + jasmine/mocha + jsdom-launcher + karma-webpack + rewire/inject-loader. Because of this I don't think that a super flexible launcher api like Karma's is well suited to Jest.

To that end I think that what you really need for jest-browser to work is the ability to create a node like environment in the browser, to which I say that webpack is the most robust and flexible tool for doing this already out there. It's plugin system can more than handle the concerns made by @cpojer above, as well as all the jest config options. Undoubtable it'd be slower, but I think that could be isolated to the browser-runner bits so that fb wouldn't mind ;P

As soon as a get a bit of time i want to see where the sore points are in the existing jest infra, and where assumptions are being made about the runtime. I think that will put us in a good place to talk about how the API's would need to change in order to support this sort of use case. I feel pretty optimistic thou, the team as already done an amazing job as modularizing and extracting jest into lots of its constituent bits :)

cpojer commented 7 years ago

I appreciate this discussion got started again. To me this has always been exciting but also risky for the project. I'll support this as much as I can and have some thoughts as well.

My ideal solution to this would be somewhere in jest-runtime and jest-environment-*. The problem is that right now; Jest requires and compiles files "just-in-time" when they are required which means they need to be synchronous. Because of the mocking feature, the entire module system and file system needs to be virtualized in the browser, as well as the entire transform pipeline with babel.

However, there are two more ways of solving this, both equally exciting:

// How it works right now, jest wraps every file in a function and passes its require implementation:
(function(require, ...) {
  const apple = require('apple');
});

// How it could work, using a babel transform:
(function*(require, ...) {
  const apple = yield require('apple');
});

This would run on the client; when we call into require we'd request the transformed module over a websocket connection.

thymikee commented 7 years ago

@cpojer I'm actually not into the topic but in case you missed it, async import() recently got to Stage-3: https://github.com/tc39/proposal-dynamic-import and I believe it's used in Webpack2 now: https://github.com/webpack/webpack/pull/3413

cpojer commented 7 years ago

@thymikee yeah good point but that also requires to write a transform from require to async import, it's very similar I guess.

jquense commented 7 years ago

i'm gonna guess that requesting files over the wire (even locally) is going to be too slow. My thought was that just shipping all the mocks for required packages would be simpler and probably faster overall? Maybe i'm missing something here but most jest mocks are statically knowable, right? detecting calls to jest.mock or require.requireMock isn't too bad. For ambiguous cases you can just ship both.

Because of the mocking feature, the entire module system and file system needs to be virtualized in the browser, as well as the entire transform pipeline with babe

This speaks to my earlier point, its a tough one. Writing a module env for the browser is going to be super fraught and complicated. The thought was to leavage work already done on that front via webpack, for this bit. Especially since almost every nook and crany of webpack is pluggable.

thattomperson commented 7 years ago

Sorry if you guys have thought of this already but if the require('./foo') needs to be done at runtime for mocking reasons, why not just write a "jest-loader" which could rewrite the require statement to something more like

let foo
if (jest.mocking('./foo')) {
    foo = require('./__mocks__/foo');
} else {
    foo = require('./foo');
}

and let webpack just include them both?

edit NVM I miss read the previous comment

cpojer commented 7 years ago

@ThatTomPerson jest-runtime does this branching already.

nhunzaker commented 7 years ago

Playing around with this a bit more. I've been experimenting with running Jest inside of an Electron browser instance:

https://gist.github.com/nhunzaker/5fc6f3ea153319e3140275914773d5bc

Electron is pretty neat because I can just require Jest inside of the browser. Trouble is that I don't have encapsulation. Test globals leak into each other.

Ideally, I think it would be neat if each test suite opened an isolated browser instance.

I'm not sure if there's really any benefit here. Eventually I think it would be neat if you could mount a React app to the browser for testing, stepping through breakpoints and watching behavior play out in a real browser.

No real questions here, but maybe it gives someone else some inspiration.

thymikee commented 7 years ago

Wow, this is super cool you're working on this!

I think you should spawn new browser in ElectronEnvironment#constructor, just like we do with spawning jsdom env. Keep us posted

cpojer commented 7 years ago

oh hell yeah @nhunzaker. I'm extremely excited about this. Do you have a guide to set this up?

If @thymikee's suggestion doesn't work, could you use iframes in runScript?

nhunzaker commented 7 years ago

@cpojer I've put up the code here:

https://github.com/nhunzaker/jest-electron-environment

I think I could use the contextIsolation option from BrowserWindow to spin up an isolated browser and arbitrarily execute some code. That would be a similar approach to spitting JS into an iframe.

What I'm struggling with is how to get the actual code for the test: https://github.com/nhunzaker/jest-electron-environment/blob/master/src/jest-environment-electron.js#L27

☝️ I'm not too familiar with Jasmine script contexts (if I have that right). Is there a way to get the string content for the test?

chrishyle commented 7 years ago

While running Jest in an electron shell is an interesting project, I think it only solves part of the issue for real world developers. While a CLI runner or an electron environment is great for our developer workflow, we have also found many issues over the years by running our unit tests in real browsers (especially older browsers) that we didn't find in those CLI environments or Chrome alone. I think we still need a way to run Jest in real browsers, even if the electron environment is successful (and I hope it is!).

nhunzaker commented 7 years ago

I completely agree. As someone less familiar with Jest internals, I found Electron to be an attractive target. Electron just works, and opens up a lot of possibilities, such as recording test sessions and stepping through browser behavior using a debugger. Still, I don't want to express neglect for real browsers with that enthusiasm.

Unfortunately, I also haven't been able to make the time for those problems. It would be cool to establish a more concrete roadmap for how we can get to testing in real browsers. I still have a lot to learn.

suchipi commented 7 years ago

My two cents: I got Jest running in NW.js, which is pretty similar to Electron, but it doesn't have the same node/browser context separation, so it's pretty easy to get the test code. Proof of concept here: https://github.com/suchipi/jest-node-nw-example

It's using the same browser globals every run right now, but creating new iframes for each test shouldn't be too hard

intellix commented 7 years ago

ChromeHeadless is now available, with support on it's way to karma-chrome-launcher: https://github.com/karma-runner/karma-chrome-launcher/pull/111 Would love to know how it performs in comparison to jsdom

joelgriffith commented 7 years ago

I've been cooking up something out in the woods in Washington, and it's this library: https://github.com/joelgriffith/navalia. This tool has the capacity to write tests in a high-level browser API, and can parallelize them in a single browser context (think of tabs). Once I harden the API a bit I'm going to write some content on how to achieve this wizardry, and folks can hopefully begin to have a better experience with E2E-style tests.

Here's a short post on how it can instrument chrome for coverage (for those of you who are curios about the API): https://codeburst.io/capturing-unused-application-code-2b7594a9fe06

MattKunze commented 7 years ago

One thing I haven't seen mentioned in this discussion (probably missed it from previous issues) is that one of one of the big wins I've seen before with browser based tests is the ability to run them on various platforms (IE/Edge, iOS on Safari, etc) to validate edge cases in those environments. It certainly doesn't cover everything you can do with automation tools, but it's been helpful to me in the past.

Getting it working on Electron or NW.js would be great from a debugging perspective, but actually being able to run it on the target platform would be even better

cpojer commented 7 years ago

Hey everyone! I thought I'd give an update on this thread, as it is one of our oldest issues. We actually made quite a lot of progress on this for Jest 21, although it's not as straightforward as "running Jest in the browser" yet. Currently tagged as beta, jest@test can run a number of modules inside of the browser, with your existing test runner there.

This gives you a way to use Jest's awesome assertion library in any other test framework.

Further, Jest is slowly migrating away from Jasmine, and with jest-circus we are building a new test runner that will also work in the browser. This is not quite ready yet, but you could experiment with it if you want to.

There are more missing pieces that are being filled in. Recently I showed a demo of Python tests being run using Jest, see pyjest. This is also not polished yet, but in the future you'll be able to use Jest's runner to drive any other test framework on any platform you like, as long as you integrate it yourself :)

Yet another big step is to "virtualize" the require/mock system and extract it from jest-runtime. This will allow to use most pieces of Jest inside a test context rather than controlling everything from the outside. Once this is done, most pieces of Jest can be run in various places.

While Jest in the future will run just as it does today, once all the pieces from above are completed, we can run almost all of Jest in almost all places. This means that Jest's runner can be used to run current Jest tests, tests in Electron, tests in any browser. Together with some other plans, we may also be able to run Jest's runner within various places.

Note that there is nobody actively working towards this right now, so if you'd like to help out, please reach out to me.

suchipi commented 7 years ago

@cpojer I'm interested in contributing. I don't think I'm familiar enough with jest-runtime to pull it apart, but could I contribute to jest-circus somehow? I'm more familiar with that problem space.

aaronabramov commented 7 years ago

@suchipi any help on jest-circus side would be appreciated! some of the things that we need to do next is:

let me know if you want to work on any of that!

suchipi commented 7 years ago

@aaronabramov Jasmine compatibility should be pretty straightforward. Is there a tracking issue outlining which features are missing that need to be added?

suchipi commented 7 years ago

I can do spelunking and figure it out if needed, but I don't want to add anything that you don't actually need; Jasmine as a whole has some overlap with expect, jest-mock, etc

rdeltour commented 7 years ago

In case it might help others, I've been experimenting with using Jest’s recent async environment API to run tests within headless Chrome with Puppeteer:

I'm pretty happy with the results!

I can't fully run everything in the browser since Puppeteer lacks an API to run vm.Scritp instances, so I have to get back to the Node context at some point... but it's very promising nonetheless! Kudos to the Jest team 👍

geovanisouza92 commented 7 years ago

@rdeltour Maybe you can get it working using https://github.com/patriksimek/vm2

rdy commented 7 years ago

I'm a maintainer of gulp-jasmine-browser which attempts to automate headless browser controls using the techniques described in this thread except using jasmine. We just finished our support for puppeteer and also have support for phanotm and slimer. I'm thinking of building a newer 2.0 version to launch the test runner and send the results back to a reporter. The newer version would be less dependent on gulp and probably utilize more of the webpack ecosystem. I'd be willing to consider adding support for jest as well if there is interest in it.

I really prefer having a real DOM to test rather than what jest currently offers.

bsuh commented 6 years ago

@cpojer What about using AMD module transform? That would allow a jest AMD implementation running in the browser to asynchronously fetch the module source from a jest runtime running on node.

Aside from refactoring the require & mock implementation out of the runtime as you mentioned in https://github.com/facebook/jest/issues/4358, several packages synchronously access the VM context, which would not be possible in a remote browser "VM".

The pattern I'm seeing is that the responsibility of setting up the VM context is distributed across packages and is mixed in with those packages' other responsibilities.

cpojer commented 6 years ago

@bsuh I think what you are saying makes sense, we could consider it for sure. I think your idea about centralizing all the VM interactions is a good start – are you willing to work on some PRs towards that?

bsuh commented 6 years ago

I can try to tackle #4358 for starters.

cpojer commented 6 years ago

Awesome! Let's continue the discussion there.

trusktr commented 6 years ago

Some more info here, https://github.com/facebook/jest/issues/3698.

And also this: https://www.npmjs.com/package/jest-electron-runner

trusktr commented 6 years ago

After fiddling with Electron and NWjs, and some of others' attempted packages, with no luck, I decided to try Karma with Chrome headless.

I was able to get it running very easily based on that article, which is great because I really need all DOM APIs to be available (even WebGL).

Really hoping Jest gets headless Chrome support sometime sooner than later. It would be great!

cole-gillespie commented 6 years ago

@nhunzaker did you ever move past electron?

nhunzaker commented 6 years ago

@coleGillespie Sorry to catch up so late. Hey!

I ended up shifting gears. Using Jest with Puppeteer ended up solving a lot of my use case. Still, I'm working on an electron app right now, and not having to mock out the electron interface sounds.... ideal.

thymikee commented 6 years ago

@nhunzaker talk to @aaronabramov about Electron integration, he's on it right now.

SimenB commented 6 years ago

https://twitter.com/aaronabramov_/status/1002996007695601664

nhunzaker commented 6 years ago

Oh wow. This is fantastic!

aaronabramov commented 6 years ago

@nhunzaker i started it as an atom test runner so i could test it on the real app, but atom has a lot of hacks/wrappers on top of electron. so before it can be generalized i'll need to clean all this hacky stuff up :)

https://github.com/facebook/nuclide/tree/master/modules/jest-atom-runner

i'm in the middle of migration from jasmine to jest with this runner (will probably take another 1-2 weeks), but once it's done i'll be moving this package out of nuclide into jest-community also any help would be appreciated :) i don't really know much about how electron apps work so i'm just trying random things right now and see what makes the build green

nhunzaker commented 6 years ago

i don't really know much about how electron apps work so i'm just trying random things right now and see what makes the build green

I'm kind of in the same boat, it's my first Electron project :), but I'd be happy to help out where I can.

neojski commented 5 years ago

https://github.com/facebook-atom/jest-electron-runner worked really well for me.

kimamula commented 5 years ago

Running Jest test code on browsers using Karma is just as easy as configuring Karma to use with Jasmine as usual and adding the following lines above where the tests are loaded.

window.jest = require('jest-mock');
window.expect = require('expect');

// if you are using snapshot testing, which cannot be run on browsers
expect.extend({
  toMatchSnapshot: () => ({ pass: true }),
  // ...
});

See https://github.com/kimamula/jest-karma-angular-demo for demo.

What I want to do is to run tests usually on JSDOM (as it is faster) but sometimes on browsers to make sure my code is compatible with target browsers, which is achieved by the above setup.

suchipi commented 5 years ago

@kimamula you can use fs-remote to run snapshot testing in browser(s); see here for an example.

One thing to note with your setup is that module mocking won't work.

kimamula commented 5 years ago

@suchipi Thanks for your feedback. I will investigate if I can make module mocking work.

guoyunhe commented 3 years ago

There is a karma-jest plugin. However, it doesn't have a stable release yet.

github-actions[bot] commented 2 years ago

This issue is stale because it has been open for 1 year with no activity. Remove stale label or comment or this will be closed in 30 days.