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

fix: Leverage Puppeteer's native support for Firefox #356

Merged
merged 1 commit into from
Apr 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ Other options are documented in [jest-dev-server](https://github.com/smooth-code

Jest Puppeteer automatically detects the best config to start Puppeteer but sometimes you may need to specify custom options. All Puppeteer [launch](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions) or [connect](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerconnectoptions) options can be specified in `jest-puppeteer.config.js` at the root of the project. Since it is JavaScript, you can use all the stuff you need, including environment.

To run Puppeteer on Firefox using [puppeteer-firefox](https://github.com/GoogleChrome/puppeteer/tree/master/experimental/puppeteer-firefox), you can set `browser` to `firefox`. By default, the value is `chromium` which will use Puppeteer on Chrome.
To run Puppeteer on Firefox, you can set the `launch.product` property to `firefox`. By default, the value is `chrome` which will use Puppeteer on Chromium.

The browser context can be also specified. By default, the browser context is shared. `incognito` is available if you want more isolation between running instances. More information available in [jest-puppeteer-environment readme](https://github.com/smooth-code/jest-puppeteer/blob/master/packages/jest-environment-puppeteer/README.md)

Expand All @@ -156,8 +156,8 @@ module.exports = {
launch: {
dumpio: true,
headless: process.env.HEADLESS !== 'false',
product: 'chrome',
},
browser: 'chromium',
browserContext: 'default',
}
```
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
"lint-staged": "^10.5.4",
"prettier": "^2.2.1",
"puppeteer": "^8.0.0",
"puppeteer-firefox": "0.5.0",
"rimraf": "^3.0.2"
},
"dependencies": {}
Expand Down
3 changes: 0 additions & 3 deletions packages/jest-environment-puppeteer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ You can specify a `jest-puppeteer.config.js` at the root of the project or defin

- `launch` <[object]> [All Puppeteer launch options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions) can be specified in config. Since it is JavaScript, you can use all stuff you need, including environment.
- `connect` <[object]> [All Puppeteer connect options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerconnectoptions) can be specified in config. This is an alternative to `launch` config, allowing you to connect to an already running instance of Chrome.
- `browser` <[string]>. Define a browser to run tests into.
- `chromium` Each test uses [puppeteer](https://npmjs.com/package/puppeteer) and runs Chromium
- `firefox` Each test uses [puppeteer-firefox](https://npmjs.com/package/puppeteer-firefox) and runs Firefox. This option requires `puppeteer-firefox` as a peer dependency.
- `browserContext` <[string]>. By default, the browser context (cookies, localStorage, etc) is shared between all tests. The following options are available for `browserContext`:
- `default` Each test starts a tab, so all tests share the same context.
- `incognito` Each tests starts an incognito window, so all tests have a separate, isolated context. Useful when running tests that could interfere with one another. (_Example: testing multiple users on the same app at once with login, transactions, etc._)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class PuppeteerEnvironment extends NodeEnvironment {

async setup() {
const config = await readConfig()
const puppeteer = getPuppeteer(config)
const puppeteer = getPuppeteer()
this.global.puppeteerConfig = config

const wsEndpoint = process.env.PUPPETEER_WS_ENDPOINT
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-environment-puppeteer/src/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let didAlreadyRunInWatchMode = false

export async function setup(jestConfig = {}) {
const config = await readConfig()
const puppeteer = getPuppeteer(config)
const puppeteer = getPuppeteer()
if (config.connect) {
browser = await puppeteer.connect(config.connect)
} else {
Expand Down
47 changes: 30 additions & 17 deletions packages/jest-environment-puppeteer/src/readConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const exists = promisify(fs.exists)

const DEFAULT_CONFIG = {
launch: {},
browser: 'chromium',
browserContext: 'default',
exitOnPageError: true,
}
Expand Down Expand Up @@ -46,24 +45,38 @@ export async function readConfig() {

// eslint-disable-next-line global-require, import/no-dynamic-require
const localConfig = await require(absConfigPath)

const product = localConfig.launch ? localConfig.launch.product : undefined

// Move browser config to launch.product
if (product === undefined && localConfig.browser) {
// eslint-disable-next-line no-console
console.warn(
'`browser` config has been deprecated and will be removed in future versions. Use `launch.product` config with `chrome` or `firefox` instead.',
)
let launch = {}
if (localConfig.launch) {
launch = localConfig.launch
}
launch.product =
localConfig.browser === 'chromium' ? 'chrome' : localConfig.browser
localConfig.launch = launch
}

// Ensure that launch.product is equal to 'chrome', or 'firefox'
if (product !== undefined && !['chrome', 'firefox'].includes(product)) {
throw new Error(`Error: Invalid product value '${product}'`)
}

return merge({}, defaultConfig, localConfig)
}

export function getPuppeteer(config) {
switch (config.browser.toLowerCase()) {
/* eslint-disable global-require, import/no-dynamic-require, import/no-extraneous-dependencies, import/no-unresolved */
case 'chromium':
try {
return require('puppeteer')
} catch (e) {
return require('puppeteer-core')
}
Comment on lines -56 to -60
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to keep the getPuppeteer function to allow using puppeteer or puppeteer-core

case 'firefox':
return require('puppeteer-firefox')
/* eslint-enable */
default:
throw new Error(
`Error: "browser" config option is given an unsupported value: ${browser}`,
)
export function getPuppeteer() {
/* eslint-disable global-require, import/no-dynamic-require, import/no-extraneous-dependencies, import/no-unresolved */
try {
return require('puppeteer')
} catch (e) {
return require('puppeteer-core')
}
/* eslint-enable */
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
browser: 'firefox',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
browser: 'chromium',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
browser: 'firefox',
launch: {
some: 'other property',
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
browser: 'firefox',
launch: {
product: 'chrome',
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
launch: {
product: 'foobar',
},
}
89 changes: 67 additions & 22 deletions packages/jest-environment-puppeteer/tests/readConfig.test.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,13 @@
import fs from 'fs'
import path from 'path'
// eslint-disable-next-line import/no-extraneous-dependencies
import pptrChromium from 'puppeteer'
// eslint-disable-next-line import/no-extraneous-dependencies
import pptrFirefox from 'puppeteer-firefox'
import { readConfig, getPuppeteer } from '../src/readConfig'
import { readConfig } from '../src/readConfig'

jest.mock('fs')

function mockExists(value) {
fs.exists.mockImplementation((path, callback) => callback(null, value))
}

describe('getPuppeteer', () => {
it('should return chromium when specified', async () => {
expect(
getPuppeteer({
browser: 'Chromium',
}),
).toBe(pptrChromium)
})
it('should return firefox when specified', async () => {
expect(
getPuppeteer({
browser: 'Firefox',
}),
).toBe(pptrFirefox)
})
})

describe('readConfig', () => {
describe('with custom config path', () => {
beforeEach(() => {
Expand Down Expand Up @@ -101,4 +80,70 @@ describe('readConfig', () => {
expect(config.server).toBeDefined()
})
})

describe('with custom product type', () => {
it('should return an error if invalid product', async () => {
process.env.JEST_PUPPETEER_CONFIG = path.resolve(
__dirname,
'__fixtures__/invalidProduct.js',
)
mockExists(true)
try {
await readConfig()
} catch (error) {
expect(error.message).toBe("Error: Invalid product value 'foobar'")
}
})
})

describe('with browser config', () => {
beforeEach(() => {
jest.spyOn(console, 'warn').mockImplementation(() => {})
})

it('should set launch.product', async () => {
process.env.JEST_PUPPETEER_CONFIG = path.resolve(
__dirname,
'__fixtures__/browserConfig.js',
)
mockExists(true)
const config = await readConfig()
// eslint-disable-next-line no-console
expect(console.warn).toHaveBeenCalledWith(
'`browser` config has been deprecated and will be removed in future versions. Use `launch.product` config with `chrome` or `firefox` instead.',
)
expect(config.launch.product).toBe('firefox')
})

it('should set launch.product to chrome', async () => {
process.env.JEST_PUPPETEER_CONFIG = path.resolve(
__dirname,
'__fixtures__/browserConfigChromium.js',
)
mockExists(true)
const config = await readConfig()
expect(config.launch.product).toBe('chrome')
})

it('should not overwrite launch', async () => {
process.env.JEST_PUPPETEER_CONFIG = path.resolve(
__dirname,
'__fixtures__/browserLaunchConfig.js',
)
mockExists(true)
const config = await readConfig()
expect(config.launch.product).toBe('firefox')
expect(config.launch.some).toBe('other property')
})

it('should not overwrite launch.product', async () => {
process.env.JEST_PUPPETEER_CONFIG = path.resolve(
__dirname,
'__fixtures__/browserLaunchProductConfig.js',
)
mockExists(true)
const config = await readConfig()
expect(config.launch.product).toBe('chrome')
})
})
})