Jest-puppeteer: How to access global page, and browser instances from globalSetup & globalTeardown?

Created on 26 Jun 2018  ·  14Comments  ·  Source: smooth-code/jest-puppeteer

I'm testing a webapp that can only have a single active session.

Therefore, I need the login process to run once prior to all tests. This seemed like a task for globalSetup.

How do I access the global page instance within globalSetup so I can run the login process prior to tests?

UPDATE: After further investigation it looks like I can access page and browser within a custom PuppeteerEnvironment with this.global.page and this.global.browser. Would this be the correct place to login globally from that browser instance prior to tests?

Furthermore it's unclear when to use setup, and teardown via extending PuppeteerEnvironment, and when to use the globalSetup and globalTeardown functions also mentioned in the docs. Any clarification on this would be appreciated 🙏

UPDATE 2: Setup and teardown in a custom PuppeteerEnvironment run before and after each test suite, which defeats the purpose of what I'm trying to accomplish with a single global login.

question ❓

Most helpful comment

@sethlesky I'm in a similar boat. The global browser isn't available within globalSetup so I'm connecting to it manually like so:

Thanks @benpink for solving this! With the variable from @liemdo the example gets a bit shorter:

// jest-setup.js
const puppeteer = require('puppeteer');
const { setup: setupPuppeteer } = require('jest-environment-puppeteer');
const utils = require('./utils.js');

// Connect to the browser created in jest-environment-puppeteer
// so we can perform browser actions once before all tests are run.
module.exports = async function globalSetup() {
  await setupPuppeteer();

  // connect to puppeteer
  const browser = await puppeteer.connect({
    browserWSEndpoint: process.env.PUPPETEER_WS_ENDPOINT,
  });

  // Open a new page
  const page = await browser.newPage();

  // Log in a user
  await utils.doLogin(page);
};

All 14 comments

UPDATE 2: Setup and teardown in a custom PuppeteerEnvironment run before and after each test suite, which defeats the purpose of what I'm trying to accomplish with a single global login.

I think you need to take into account the fact that all your tests will run on a single instance of puppeteer. puppeteer.launch happens only once in global.js of jest-environment-puppeteer and a webservice endpoint is written down. All subsequent pages seemed to be launched from this instance using puppeteer.connect() and the previously defined webservice endpoint inside PuppeteerEnvironment.js.

If your login state is "preserved" with local storage / sessions, I'm confident in saying that it will remain across all tests.

Moving forward, I think having a login function inside a beforeAll (in jest) hook would achieve what you are looking for.

globalSetup and globalTeardown is not relative to this library, it is relative to Jest. These methods are run before starting Jest tests and spread them into workers.

So the good place for your script is a beforeAll or an custom environment but not globalSetup.

I appreciate the thoughtful replies, but am unfortunately still misunderstanding where to place beforeAll so it runs only once across all test files.

Based on Jest docs, beforeAll seems to run before all tests scoped to the single file it's contained within. I only need the login script to be run once across all tests (as Puppeteer shares the session across tests as @Niceplace clarified). Where would I place beforeAll so it runs once across all files?

Likewise, setup within a custom environment runs before each test suite/file. This results in the login script to be executed once for every test file.

This is why I was looking to globalSetup which I now understand won't work either.

@sethlesky You are right, I did not answer your question correctly. I'm digging in Jest's configuration and I think I might have found what you need. I'm just going to put a bunch of links here so you can explore all the options (some of these are used in jest-environment-puppeteer).

@Niceplace Thanks for the links to the additional docs. It's starting to seem like the jest way running a one-time setup script is within globalSetup. But based on @neoziro's comment, that runs prior to jest-environment-puppeteer, so I wouldn't have access to the Puppeteer instance yet.

Is there a way for setup() in an extended PuppeteerEnvironment to only run once, instead of prior to each test file?

Within a custom environment, I could check to see if a file I write to disk upon init exists, and only run the init sequence when that file doesn't exist. I've tried adding a boolean like initCompleted to this.global, but it doesn't seem to stick between test files. It seems there must be a better way.

Thanks again for everyone's time 👍

@sethlesky I'm in a similar boat. The global browser isn't available within globalSetup so I'm connecting to it manually like so:

// jest-setup.js

const fs = require('fs')
const os = require('os')
const path = require('path')
const puppeteer = require('puppeteer')
const { setup: setupPuppeteer } = require('jest-environment-puppeteer')
const utils = require('./utils.js')

// Connect to the browser created in jest-environment-puppeteer
// so we can perform browser actions once before all tests are run.
module.exports = async function globalSetup() {
  await setupPuppeteer()

  // This is the path to the wsEndpoint as defined in jest-environment-puppeteer:
  // https://github.com/smooth-code/jest-puppeteer/blob/master/packages/jest-environment-puppeteer/src/constants.js#L4
  const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup')

  // get the wsEndpoint
  const wsEndpoint = fs.readFileSync(path.join(DIR, 'wsEndpoint'), 'utf8')
  if (!wsEndpoint) {
    throw new Error('wsEndpoint not found')
  }

  // connect to puppeteer
  const browser = await puppeteer.connect({
    browserWSEndpoint: wsEndpoint,
  })

  // Open a new page
  const page = await browser.newPage()

  // Log in a user
  await utils.doLogin(page)
}

I'm using the above via the globalSetup option in the jest config, like so:

// jest.config.js

module.exports = {
  globalSetup: "<rootDir>/jest-setup.js",
  preset: "jest-puppeteer",
}

This will open a new page and perform a login before any tests are run.

Relevant doc info here:
https://github.com/smooth-code/jest-puppeteer#create-your-own-globalsetup-and-globalteardown

@sethlesky I think you have already known the differences between globalSetup/globalTeardown and PuppeteerEnvironment. Previous one only runs once in whole tests, but latter one runs for every test suite, simply like every test file. But in globalSetup you cannot get page and browser, which are set in PuppeteerEnvironment. If your site can keep cookie/session in browser, you can connect to browser in your globalSetup and then open page manually to login, then you can close that page. Something like below, note I don't test it.

// in your custom globalSetup
const {setup: globalPuppeterSetup} = require('jest-environment-puppeteer');
async setup() {
   await globalPuppeterSetup();
   // connect browser and open new page to login
   const browser = await puppeteer.connect();
   cont page = await browser.newPage();
   // Now you have page to login

   // close page
   await page.close();   
}

@benpink

I get the following error when I try your solution, do I have to add something to my tmp directory?

Error: ENOENT: no such file or directory, open '/tmp/jest_puppeteer_global_setup/wsEndpoint'
at Object.openSync (fs.js:436:3)
at Object.readFileSync (fs.js:341:35)
at globalSetup (/srv/http/ctms-testing/jest-setup.js:15:25)

It should be fixed in the latest version, try to upgrade.

@benpink - I'm having the same issue and I tried the solution you mentioned above. It works, but jest-puppeteer opens a new browser later on. So the cookie that was set on the browser I opened in globalSetup is no longer access-able on tests.

The only way to make it work is to create an browser object that is being carried somehow to jest-puppeter - and I'm not sure that's possible without modifying jest-puppeteer code.

@sethlesky Did you settle on a solution for this? I'm in the exact same boat - I want to organize tests into different files or test suites, but I don't want to have to run log-in automation before every test.

My understand is also that this is what globalSetup would be for, but I am unable to access Page variables.


@neoziro I'm hoping to get more context into this issue and the fact that it is closed - is the solution proposed by @benpink more or less recommended? Are there any better ways to have some code (commonly instantiate and log-in) happen before all test suites?

Hello @BenjaminCaffrey, you can't access page in globalSetup, actually the only solution is to create a custom environment or a custom beforeAll.

@dnovakovic15 In the latest version, you can get the wsEndpoint by using process.env.PUPPETEER_WS_ENDPOINT

 const browser = await puppeteer.connect({
    browserWSEndpoint: process.env.PUPPETEER_WS_ENDPOINT
  });

@sethlesky I'm in a similar boat. The global browser isn't available within globalSetup so I'm connecting to it manually like so:

Thanks @benpink for solving this! With the variable from @liemdo the example gets a bit shorter:

// jest-setup.js
const puppeteer = require('puppeteer');
const { setup: setupPuppeteer } = require('jest-environment-puppeteer');
const utils = require('./utils.js');

// Connect to the browser created in jest-environment-puppeteer
// so we can perform browser actions once before all tests are run.
module.exports = async function globalSetup() {
  await setupPuppeteer();

  // connect to puppeteer
  const browser = await puppeteer.connect({
    browserWSEndpoint: process.env.PUPPETEER_WS_ENDPOINT,
  });

  // Open a new page
  const page = await browser.newPage();

  // Log in a user
  await utils.doLogin(page);
};

Was this page helpful?
0 / 5 - 0 ratings