Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example to testEnvironment in Configuration.md #4690

Merged
merged 1 commit into from
Oct 15, 2017

Conversation

xfumihiro
Copy link
Contributor

No description provided.

@cpojer
Copy link
Member

cpojer commented Oct 13, 2017

Can you explain how this example code actually works in practice? How does puppeteer use the vm or run individual scripts?

@xfumihiro
Copy link
Contributor Author

wouldn't that take too much space?
Or maybe we just show an example like:

class CustomEnvironment extends NodeEnvironment {
  constructor(config) {
     super(config);
  }
 
   async setup() {
     await super.setup();
     await someSetupTask();
   }
 
   async teardown() {
     await someTeardownTask();
     await super.teardown();
   }
 
   runScript(script) {
     return super.runScript(script);
   }
 }

@cpojer
Copy link
Member

cpojer commented Oct 13, 2017

The amount of space it needs doesn't matter to me so much, we should make sure the docs are easy to understand. We can also link to a longer example.

@xfumihiro
Copy link
Contributor Author

In my case, I'm using it to launch/close the puppeteer browser instance.
All the test scripts can then run inside separated pages of the same browser.
This greatly reduce the overall test time.

@rzane
Copy link

rzane commented Oct 14, 2017

@xfumihiro, I'm also trying to use the same puppeteer browser across all tests, but the example you've provided doesn't do that (as best I can tell).

The example you showed in #4506 seems to work just like beforeAll and afterAll, which means the browser gets re-launched for every test file. I'd like to share the same browser across all tests.

@xfumihiro
Copy link
Contributor Author

@rzane you're right. Every script setup/teardown their own environment. More work need to be done to get this working. 😞

@xfumihiro
Copy link
Contributor Author

xfumihiro commented Oct 15, 2017

Just published a npm package to do this.
It's basically a wrapper of Puppeteer environment for Jest.
First it launches puppeteer and keep the browserWSEndpoint at global.browserWSEndpoint.
Then it run Jest tests.
Each test case can have their puppeteer instance connect to the browser via global.browserWSEndpoint and teardown when finished.
Finally, it teardowns the browser instance via a custom Jest result processor.
Here's an Example Usage

@rzane
Copy link

rzane commented Oct 15, 2017

Yeah, that seems like it will work, but I was surprised to find that jest doesn't have a beforeSuite (e.g. a hook that runs before everything). This feature was requested in #3832, #4565, and #4118, and all of those issues were resolved by saying that a custom environment would achieve the goal of a global setup.

@xfumihiro
Copy link
Contributor Author

Maybe it's better this way for sandboxing each test.
Having a global environment seems against that.

For puppeteer use case, I think this is better than having Jest to take care the browser lifecycle.
Each test just need the endpoint to connect and the wrapper itself isn't too heavy weighted.

@rzane
Copy link

rzane commented Oct 15, 2017

Yeah, what you're saying makes sense, and you've definitely come up with a clever solution. But, the benefit of using a Jest environment is that you can switch it on a per-test basis. For example, I have unit tests the run really quickly and don't require a browser, then, I have integration tests that need a browser and need the server to be started (Phoenix in this case, so it's not node).

So, for my integration tests, I can just add:

/**
 * jest-environment ./path/to/my/env
 */

This way, if I don't run any integration tests, I can skip the browser/web server boot times.

@xfumihiro
Copy link
Contributor Author

From my understanding, the testEnvironment is sandboxed.
So the setup/teardown is triggered on every test script (file) or test suite.
(It's still global, just not single shot)
If you want something that is triggered once on all test suites, maybe try a custom runner.

@cpojer cpojer merged commit dc99694 into jestjs:master Oct 15, 2017
@michaeljones
Copy link

From looking at the custom jasmine2 runner that comes with jest, it is quite involved. It would be a shame to fall out of sync with that file in order to add a little some extra. I am in a similar position of wanting to transition from mocha as I love the jest watch mode but we have database setup & teardown that would ideally be run only once for the whole test run.

Love jest though, thanks for the great project.

@xfumihiro
Copy link
Contributor Author

@rzane @michaeljones #4716

@kdzwinel
Copy link

kdzwinel commented Oct 25, 2017

We are also playing with Puppeteer+Jest combo and I've run into the same issue. Cost of starting the browser is very high (we moved tests from CasperJS and run times went up 2x), so we want to reuse instances. What we do ATM to solve this is we keep our own pool of browsers:

const NodeEnvironment = require('jest-environment-node');
const puppeteer = require('puppeteer');
const debug = Boolean(process.env.debug);
const browserPool = [];

async function setupPuppeter() {
  let browserSettings = {};

  if (debug) {
    browserSettings = {
      headless: false,
      slowMo: 25
    };
  }

  return puppeteer.launch(browserSettings);
}

class PuppeteerEnvironment extends NodeEnvironment {
  async setup() {
    super.setup();

    let browser, page;

    if (browserPool.length) {
      const item = browserPool.pop();

      browser = item.browser;
      page = item.page;
    } else {
      browser = await setupPuppeter();
      page = await browser.newPage();
    }

    this._browser = browser;
    this._page = page;

    // expose browser and page to tests
    this.context.browser = browser;
    this.context.page = page;
  }

  async teardown() {
    browserPool.push({
      browser: this._browser,
      page: this._page
    });

    await super.teardown();
  }
}

module.exports = PuppeteerEnvironment;

However, this requires us to use --forceExit because we have nowhere to kill all these browsers 😢

If someone has any ideas how to improve this, I'd love to hear them!

[EDIT] Oh! globalTeardown FTW!

@thymikee
Copy link
Collaborator

@kdzwinel how about spawning just one browser before class init and just inject it into the env, will that work?
As for tearing down the browser, in parallel run Jest will use os.cpus().length - 1 cores (in watch mode os.cpus().length/2 and in --runInBand just 1) or as many as you specify in --maxWorkers , so you could count if that's the last one and close the browser accordingly.

@kdzwinel
Copy link

@thymikee We want multiple browsers so that they run on separate cores in parallel. I like your idea with finding the last teardown call though. However, I don't see how I can figure out if current teardown is the last one based on number of cores. I'd need to have number of tests and when noOfTests === noOfTeardowns do the cleaning. Is number of tests exposed to the environment somehow?

@thymikee
Copy link
Collaborator

Oh damn, I've never given it deeper thoughts and had been under impression that test environment spins once per worker, but instead it's set up for every test 😱 (which totally makes sense). Sorry about that. There's no way currently to determine number of tests inside environment. I don't think I'll come up with something not requiring --forceExit today because it's late, but I'll try to revisit the topic later.

@github-actions
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 13, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants