Skip to content

Commit 147f810

Browse files
authored
enh: automatically determine coverage data (#157)
1 parent cc30ce4 commit 147f810

File tree

3 files changed

+56
-20
lines changed

3 files changed

+56
-20
lines changed

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,13 @@ await jestPlaywright.debug()
150150

151151
## Tracking the coverage
152152

153-
It's possible to track the coverage of the end-to-end tests with the [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul) Babel plugin configured. It needs to be included in the web application which you are gonna test otherwise it won't work. To use it, you have to set `collectCoverage` in the `jest-playwright.config.js` to `true` and add the corresponding `saveCoverage(page)` call to your tests like that:
153+
It's possible to track the coverage of the end-to-end tests with the [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul) Babel plugin configured. It needs to be included in the web application which you are gonna test otherwise it won't work. To use it, you have to set `collectCoverage` in the `jest-playwright.config.js` to `true`. Per default the test coverage will be automatically saved after each navigation change (`beforeunload` event). If a certain code path is not covered, you can manually call and add the corresponding `saveCoverage(page)` call to your tests like that:
154154

155155
```js
156-
afterEach(async () => {
157-
await jestPlaywright.saveCoverage(page)
158-
})
156+
await jestPlaywright.saveCoverage(page)
159157
```
160158

161-
With this change, it will write the coverage data to the `.nyc_output/coverage.json` file which can be transformed using [`nyc`](https://github.com/istanbuljs/nyc#readme) to the lcov format:
159+
By using coverage collection, it will write the coverage data to the `.nyc_output/coverage.json` file which can be transformed using [`nyc`](https://github.com/istanbuljs/nyc#readme) to the lcov format:
162160

163161
```
164162
npx nyc report --reporter=lcovonly

src/PlaywrightEnvironment.ts

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable no-console */
22
import type { Event, State } from 'jest-circus'
3-
import type { Browser, Page } from 'playwright-core'
3+
import type { Browser, Page, BrowserContext } from 'playwright-core'
44
import type {
55
JestPlaywrightConfig,
66
GenericBrowser,
@@ -14,7 +14,7 @@ import {
1414
getPlaywrightInstance,
1515
readConfig,
1616
} from './utils'
17-
import { savePageCoverage } from './coverage'
17+
import { saveCoverageOnPage, saveCoverageToFile } from './coverage'
1818

1919
const handleError = (error: Error): void => {
2020
process.emit('uncaughtException', error)
@@ -46,14 +46,15 @@ const getBrowserPerProcess = async (
4646
}
4747
}
4848

49-
export const getPlaywrightEnv = (basicEnv = 'node') => {
49+
export const getPlaywrightEnv = (basicEnv = 'node'): unknown => {
5050
// eslint-disable-next-line @typescript-eslint/no-var-requires
5151
const RootEnv = require(basicEnv === 'node'
5252
? 'jest-environment-node'
5353
: 'jest-environment-jsdom')
5454

5555
return class PlaywrightEnvironment extends RootEnv {
5656
readonly _config: JestPlaywrightJestConfig
57+
_jestPlaywrightConfig!: JestPlaywrightConfig
5758

5859
constructor(config: JestPlaywrightJestConfig) {
5960
super(config)
@@ -62,11 +63,20 @@ export const getPlaywrightEnv = (basicEnv = 'node') => {
6263

6364
async setup(): Promise<void> {
6465
const { rootDir, wsEndpoint, browserName } = this._config
65-
const config = await readConfig(rootDir)
66-
config.connectOptions = { wsEndpoint }
66+
this._jestPlaywrightConfig = await readConfig(rootDir)
67+
if (
68+
wsEndpoint &&
69+
!this._jestPlaywrightConfig.connectOptions?.wsEndpoint
70+
) {
71+
this._jestPlaywrightConfig.connectOptions = { wsEndpoint }
72+
}
6773
const browserType = getBrowserType(browserName)
68-
const { exitOnPageError, selectors } = config
69-
let { contextOptions } = config
74+
const {
75+
exitOnPageError,
76+
selectors,
77+
collectCoverage,
78+
} = this._jestPlaywrightConfig
79+
let { contextOptions } = this._jestPlaywrightConfig
7080
const device = getDeviceType(this._config.device)
7181
const {
7282
name,
@@ -100,9 +110,21 @@ export const getPlaywrightEnv = (basicEnv = 'node') => {
100110
this.global.browser = await getBrowserPerProcess(
101111
playwrightInstance,
102112
browserType,
103-
config,
113+
this._jestPlaywrightConfig,
104114
)
105115
this.global.context = await this.global.browser.newContext(contextOptions)
116+
if (collectCoverage) {
117+
;(this.global.context as BrowserContext).exposeFunction(
118+
'reportCodeCoverage',
119+
saveCoverageToFile,
120+
)
121+
;(this.global.context as BrowserContext).addInitScript(() =>
122+
window.addEventListener('beforeunload', () => {
123+
// @ts-ignore
124+
reportCodeCoverage(window.__coverage__)
125+
}),
126+
)
127+
}
106128
this.global.page = await this.global.context.newPage()
107129
if (exitOnPageError) {
108130
this.global.page.on('pageerror', handleError)
@@ -147,7 +169,7 @@ export const getPlaywrightEnv = (basicEnv = 'node') => {
147169
})
148170
},
149171
saveCoverage: async (page: Page): Promise<void> =>
150-
savePageCoverage(page, config.collectCoverage),
172+
saveCoverageOnPage(page, this._jestPlaywrightConfig.collectCoverage),
151173
}
152174
}
153175

@@ -164,10 +186,22 @@ export const getPlaywrightEnv = (basicEnv = 'node') => {
164186
}
165187

166188
async teardown(): Promise<void> {
167-
const { page, browser } = this.global
189+
const { browser, context, page } = this.global
190+
const { collectCoverage } = this._jestPlaywrightConfig
168191
if (page) {
169192
page.removeListener('pageerror', handleError)
170193
}
194+
if (collectCoverage) {
195+
await Promise.all(
196+
(context as BrowserContext).pages().map((p) =>
197+
p.close({
198+
runBeforeUnload: true,
199+
}),
200+
),
201+
)
202+
// wait until coverage data was sent successfully to the exposed function
203+
await new Promise((resolve) => setTimeout(resolve, 10))
204+
}
171205

172206
if (browser) {
173207
await browser.close()

src/coverage.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@ export const setupCoverage = async (): Promise<void> => {
2424
await fsAsync.mkdir(COV_MERGE_DIR)
2525
}
2626

27-
export const savePageCoverage = async (
27+
export const saveCoverageToFile = async (coverage: unknown): Promise<void> => {
28+
await fsAsync.writeFile(
29+
path.join(COV_MERGE_DIR, `${uuid.v4()}.json`),
30+
JSON.stringify(coverage),
31+
)
32+
}
33+
34+
export const saveCoverageOnPage = async (
2835
page: Page,
2936
collectCoverage: boolean,
3037
): Promise<void> => {
@@ -36,10 +43,7 @@ export const savePageCoverage = async (
3643
}
3744
const coverage = await page.evaluate(`window.__coverage__`)
3845
if (coverage) {
39-
await fsAsync.writeFile(
40-
path.join(COV_MERGE_DIR, `${uuid.v4()}.json`),
41-
JSON.stringify(coverage),
42-
)
46+
await saveCoverageToFile(coverage)
4347
}
4448
}
4549

0 commit comments

Comments
 (0)