diff --git a/.gitignore b/.gitignore index a1028105e3b80..ad22efd8bbde2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ /test-results/ +/coverage/ /tests/coverage-report .local-browsers/ /.dev_profile* diff --git a/eslint.config.mjs b/eslint.config.mjs index 8eda40f3a5675..d679710ebe26a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -41,6 +41,7 @@ const ignores = [ "*.js", "**/.cache/", "**/*.d.ts", + "coverage/", "index.d.ts", "node_modules/", "output/", diff --git a/package-lock.json b/package-lock.json index 8b50e490f8147..f5b3beacc6764 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,6 +54,7 @@ "@types/diff": "7.0.2", "@types/formidable": "2.0.6", "@types/ini": "4.1.1", + "@types/istanbul-lib-instrument": "1.7.8", "@types/node": "20.19.43", "@types/pngjs": "6.0.5", "@types/progress": "2.0.7", @@ -94,6 +95,7 @@ "graceful-fs": "4.2.11", "https-proxy-agent": "9.1.0", "ini": "7.0.0", + "istanbul-lib-instrument": "6.0.3", "jest-matcher-utils": "30.4.1", "jest-message-util": "30.4.1", "jpeg-js": "0.4.4", @@ -1993,6 +1995,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jest/diff-sequences": { "version": "30.4.0", "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", @@ -2798,6 +2810,23 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/babel-generator": { + "version": "6.25.8", + "resolved": "https://registry.npmjs.org/@types/babel-generator/-/babel-generator-6.25.8.tgz", + "integrity": "sha512-f5l89J0UpYhTE6TFCxy3X+8pJVru1eig1fcvF9qHmOk9h1VxZimd+++tu5GShntCOdhE/MoZZ0SlpGTyh4XrKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel-types": "*" + } + }, + "node_modules/@types/babel-types": { + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.16.tgz", + "integrity": "sha512-5QXs9GBFTNTmilLlWBhnsprqpjfrotyrnzUdwDrywEL/DA4LuCWQT300BTOXA3Y9ngT9F2uvmCoIxI6z8DlJEA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/chrome": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.2.0.tgz", @@ -2891,6 +2920,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/istanbul-lib-instrument": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.8.tgz", + "integrity": "sha512-y8t6GUkn5bTXry7Zu/2HjTakxLNtvKbIQnAiGR2M3orrdZF+zp1J9ZAKfj3VM1k3sJodkjEcWfdCJ0bEAKp6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel-generator": "*", + "@types/babel-types": "*", + "@types/istanbul-lib-coverage": "*", + "source-map": "^0.6.1" + } + }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", @@ -6688,6 +6730,46 @@ "dev": true, "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", diff --git a/package.json b/package.json index 3bd7df4a818f9..8c112d5b90358 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "@types/diff": "7.0.2", "@types/formidable": "2.0.6", "@types/ini": "4.1.1", + "@types/istanbul-lib-instrument": "1.7.8", "@types/node": "20.19.43", "@types/pngjs": "6.0.5", "@types/progress": "2.0.7", @@ -137,6 +138,7 @@ "graceful-fs": "4.2.11", "https-proxy-agent": "9.1.0", "ini": "7.0.0", + "istanbul-lib-instrument": "6.0.3", "jest-matcher-utils": "30.4.1", "jest-message-util": "30.4.1", "jpeg-js": "0.4.4", diff --git a/packages/html-reporter/vite.config.ts b/packages/html-reporter/vite.config.ts index 93ec7ddde5153..a73587e9d1d9d 100644 --- a/packages/html-reporter/vite.config.ts +++ b/packages/html-reporter/vite.config.ts @@ -20,6 +20,7 @@ import { bundle } from './bundle'; import { storyTypes } from './tests/storyTypes'; import packageJSON from './package.json'; import path from 'path'; +import { istanbul } from '../../utils/build/viteIstanbul'; // https://vitejs.dev/config/ export default defineConfig({ @@ -28,6 +29,7 @@ export default defineConfig({ react(), bundle(), storyTypes({ prefix: packageJSON.name, src: path.resolve(__dirname, 'src'), outFile: path.resolve(__dirname, 'tests/stories.d.ts') }), + ...(process.env.PWTEST_COVERAGE ? [istanbul()] : []), ], resolve: { alias: { diff --git a/packages/injected/src/coverageScript.ts b/packages/injected/src/coverageScript.ts index 1852ece1602be..be3116f8ee081 100644 --- a/packages/injected/src/coverageScript.ts +++ b/packages/injected/src/coverageScript.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { kCoverageStashError, kCoverageStashPrefix } from '@isomorphic/istanbulCoverage'; +import { kCoverageStashError, kCoverageStashPrefix, takeBranchCounters, takeCounters } from '@isomorphic/istanbulCoverage'; import type { IstanbulCoverage, IstanbulCoverageDelta, IstanbulFileCoverageDelta } from '@isomorphic/istanbulCoverage'; @@ -134,32 +134,3 @@ export function takeCoverageStashes(global: typeof globalThis, sessionId: string } return result; } - -function takeCounters(counters: { [key: string]: number }): { [key: string]: number } | undefined { - let result: { [key: string]: number } | undefined; - for (const key of Object.keys(counters)) { - const count = counters[key]; - if (!count) - continue; - if (!result) - result = {}; - result[key] = count; - counters[key] = 0; - } - return result; -} - -// Branch counters are positional, so a hit branch is reported with the whole array. -function takeBranchCounters(counters: { [key: string]: number[] }): { [key: string]: number[] } | undefined { - let result: { [key: string]: number[] } | undefined; - for (const key of Object.keys(counters)) { - const counts = counters[key]; - if (!counts.some(Boolean)) - continue; - if (!result) - result = {}; - result[key] = counts.slice(); - counts.fill(0); - } - return result; -} diff --git a/packages/isomorphic/istanbulCoverage.ts b/packages/isomorphic/istanbulCoverage.ts index 7c0111c6f4371..fb90fc1d15125 100644 --- a/packages/isomorphic/istanbulCoverage.ts +++ b/packages/isomorphic/istanbulCoverage.ts @@ -99,3 +99,32 @@ export function mergeIstanbulCoverage(into: Map, d } } } + +export function takeCounters(counters: { [key: string]: number }): { [key: string]: number } | undefined { + let result: { [key: string]: number } | undefined; + for (const key of Object.keys(counters)) { + const count = counters[key]; + if (!count) + continue; + if (!result) + result = {}; + result[key] = count; + counters[key] = 0; + } + return result; +} + +// Branch counters are positional, so a hit branch is reported with the whole array. +export function takeBranchCounters(counters: { [key: string]: number[] }): { [key: string]: number[] } | undefined { + let result: { [key: string]: number[] } | undefined; + for (const key of Object.keys(counters)) { + const counts = counters[key]; + if (!counts.some(Boolean)) + continue; + if (!result) + result = {}; + result[key] = counts.slice(); + counts.fill(0); + } + return result; +} diff --git a/packages/isomorphic/trace/snapshotRenderer.ts b/packages/isomorphic/trace/snapshotRenderer.ts index 63612c79ae3ad..cf2e96a46a2e9 100644 --- a/packages/isomorphic/trace/snapshotRenderer.ts +++ b/packages/isomorphic/trace/snapshotRenderer.ts @@ -320,6 +320,8 @@ function generateNonce(): string { } function snapshotScript(viewport: ViewportSize, ...targetIds: (string | undefined)[]) { + // Stringified into the snapshot, keep it free of counters. + /* istanbul ignore next */ function applyPlaywrightAttributes(blankSnapshotUrl: string, viewport: ViewportSize, ...targetIds: (string | undefined)[]) { // eslint-disable-next-line no-restricted-globals const win = window; diff --git a/packages/trace-viewer/src/sw/main.ts b/packages/trace-viewer/src/sw/main.ts index e4adaac67e726..6042fc81f3fd5 100644 --- a/packages/trace-viewer/src/sw/main.ts +++ b/packages/trace-viewer/src/sw/main.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { takeBranchCounters, takeCounters } from '@isomorphic/istanbulCoverage'; import { SnapshotServer } from '@isomorphic/trace/snapshotServer'; import { TraceLoader } from '@isomorphic/trace/traceLoader'; import { TraceVersionError } from '@isomorphic/trace/traceModernizer'; @@ -21,6 +22,8 @@ import { TraceVersionError } from '@isomorphic/trace/traceModernizer'; import { Progress, splitProgress } from './progress'; import { FetchTraceLoaderBackend, ZipTraceLoaderBackend } from './traceLoaderBackends'; +import type { IstanbulCoverage } from '@isomorphic/istanbulCoverage'; + type Client = { id: string; url: string; @@ -152,6 +155,9 @@ async function doFetch(event: FetchEvent): Promise { if (relativePath === '/ping') return new Response(null, { status: 200 }); + if (relativePath === '/coverage') + return new Response(JSON.stringify(takeCoverage()), { status: 200, headers: { 'Content-Type': 'application/json' } }); + const isNavigation = !!event.resultingClientId; const client = event.clientId ? await self.clients.get(event.clientId) : undefined; @@ -233,6 +239,18 @@ async function doFetch(event: FetchEvent): Promise { return fetch(event.request); } +// Reading resets the counters, so every response is a delta. +function takeCoverage(): IstanbulCoverage { + const coverage: IstanbulCoverage = (self as any).__coverage__ || {}; + const result = JSON.parse(JSON.stringify(coverage)); + for (const file of Object.values(coverage)) { + takeCounters(file.s); + takeCounters(file.f); + takeBranchCounters(file.b); + } + return result; +} + function downloadHeaders(searchParams: URLSearchParams): Headers | undefined { const name = searchParams.get('dn'); const contentType = searchParams.get('dct'); diff --git a/packages/trace-viewer/vite.config.ts b/packages/trace-viewer/vite.config.ts index ac30801db686d..56a9cb03e5abc 100644 --- a/packages/trace-viewer/vite.config.ts +++ b/packages/trace-viewer/vite.config.ts @@ -20,13 +20,15 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { bundle } from './bundle'; +import { istanbul } from '../../utils/build/viteIstanbul'; // https://vitejs.dev/config/ export default defineConfig({ base: '', plugins: [ react(), - bundle() + bundle(), + ...(process.env.PWTEST_COVERAGE ? [istanbul()] : []), ], define: { 'process.env': {}, diff --git a/tests/config/serviceWorkerCoverage.ts b/tests/config/serviceWorkerCoverage.ts new file mode 100644 index 0000000000000..9292466c8002d --- /dev/null +++ b/tests/config/serviceWorkerCoverage.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +type PageLike = { evaluate(pageFunction: any): Promise }; + +// Folds the worker's counters, served by its coverage route in instrumented builds, +// into the page's, from where the tracing coverage reads. +export async function mergeServiceWorkerCoverage(page: PageLike) { + if (!process.env.PWTEST_COVERAGE) + return; + await page.evaluate(async () => { + if (!navigator.serviceWorker?.controller) + return; + const response = await fetch('coverage'); + if (!response.ok) + return; + const taken = await response.json(); + const coverage = (window as any).__coverage__ ??= {}; + for (const [file, data] of Object.entries(taken)) { + const existing = coverage[file]; + if (!existing) { + coverage[file] = data; + continue; + } + for (const key of Object.keys(data.s)) + existing.s[key] += data.s[key]; + for (const key of Object.keys(data.f)) + existing.f[key] += data.f[key]; + for (const key of Object.keys(data.b)) + existing.b[key] = existing.b[key].map((count: number, i: number) => count + data.b[key][i]); + } + }).catch(() => {}); +} diff --git a/tests/config/traceViewerFixtures.ts b/tests/config/traceViewerFixtures.ts index d580fcc488921..949dc08549bca 100644 --- a/tests/config/traceViewerFixtures.ts +++ b/tests/config/traceViewerFixtures.ts @@ -18,6 +18,7 @@ import type { Fixtures, FrameLocator, Locator, Page, Browser, BrowserContext } f import { step } from './baseTest'; import path from 'path'; import { CommonFixtures, TestChildProcess } from './commonFixtures'; +import { mergeServiceWorkerCoverage } from './serviceWorkerCoverage'; type BaseTestFixtures = CommonFixtures & { context: BrowserContext; @@ -155,7 +156,7 @@ class TraceViewerPage { export const traceViewerFixtures: Fixtures = { showTraceViewer: async ({ playwright, childProcess, browserName, channel }, use, testInfo) => { const browsers: Browser[] = []; - const tracings: any[] = []; + const tracedPages: Page[] = []; await use(async (trace: string | undefined, { host, port, stdin, cwd } = {}) => { // In WSL the browser runs in the guest and reaches the host over mirrored networking, // which mirrors the IPv4 loopback but not the host's IPv6 `::1`. Both `--host localhost` @@ -182,19 +183,23 @@ export const traceViewerFixtures: Fixtures { ] : [ ['html', { open: 'on-failure', title: 'Playwright Library Tests' }] ]; + if (process.env.PWTEST_COVERAGE) + result.push(['coverage', { outputDir: path.join(__dirname, '..', '..', 'coverage', 'trace-viewer') }]); return result; }; diff --git a/tests/playwright-test/playwright.config.ts b/tests/playwright-test/playwright.config.ts index e942895262c69..3ad2e95597e13 100644 --- a/tests/playwright-test/playwright.config.ts +++ b/tests/playwright-test/playwright.config.ts @@ -29,10 +29,16 @@ const reporters = () => { ] : [ ['list'] ]; + if (process.env.PWTEST_COVERAGE) + result.push(['coverage', { outputDir: path.join(__dirname, '..', '..', 'coverage', 'html-reporter') }]); return result; }; export default defineConfig({ timeout: 30000, + use: { + // Coverage of the html reporter and trace viewer pages, built with PWTEST_COVERAGE=1. + trace: process.env.PWTEST_COVERAGE ? { mode: 'on', snapshots: false, screenshots: false, coverage: true } : 'off', + }, forbidOnly: !!process.env.CI, workers: undefined, snapshotPathTemplate: '__screenshots__/{testFilePath}/{arg}{ext}', diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 5bc4740eaa1f7..b9575303b806b 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -20,6 +20,7 @@ import url from 'url'; import * as yazl from 'yazl'; import { test as baseTest, expect as baseExpect, cliEntrypoint, createImage } from './playwright-test-fixtures'; import { iso, utils } from '../../packages/playwright-core/lib/coreBundle'; +import { mergeServiceWorkerCoverage } from '../config/serviceWorkerCoverage'; type HttpServer = utils.HttpServer; @@ -27,6 +28,11 @@ const { msToString } = iso; const { spawnAsync } = utils; const test = baseTest.extend<{ showReport: (reportFolder?: string) => Promise }>({ + page: async ({ page }, use) => { + await use(page); + // Before the page is collected. + await mergeServiceWorkerCoverage(page); + }, showReport: async ({ page }, use, testInfo) => { let server: HttpServer | undefined; await use(async (reportFolder?: string) => { diff --git a/tests/playwright-test/stable-test-runner/package-lock.json b/tests/playwright-test/stable-test-runner/package-lock.json index 1b109a8894922..8c3dc319357c9 100644 --- a/tests/playwright-test/stable-test-runner/package-lock.json +++ b/tests/playwright-test/stable-test-runner/package-lock.json @@ -5,16 +5,16 @@ "packages": { "": { "dependencies": { - "@playwright/test": "^1.64.0-alpha-2026-09-14" + "@playwright/test": "^1.64.0-alpha-2026-09-18" } }, "node_modules/@playwright/test": { - "version": "1.64.0-alpha-2026-09-14", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.0-alpha-2026-09-14.tgz", - "integrity": "sha512-jSB1pCsT/j6ZLHnmc4pBs/9LDJrNJvvV4zmzthdufRk0RaijJTZ9dVqk29qAO6s1+I3u2vme8nsHqjmoEK1sTg==", + "version": "1.64.0-alpha-2026-09-18", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.0-alpha-2026-09-18.tgz", + "integrity": "sha512-IorNcynBpmU85uiXy7jmW4z0DmY4gc1xwihjWWEv+nbcuSWxoNs/elc5S3oujq3o4EXi+seRCbBv/bGpr0GbTw==", "license": "Apache-2.0", "dependencies": { - "playwright": "1.64.0-alpha-2026-09-14" + "playwright": "1.64.0-alpha-2026-09-18" }, "bin": { "playwright": "cli.js" @@ -24,12 +24,12 @@ } }, "node_modules/playwright": { - "version": "1.64.0-alpha-2026-09-14", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.0-alpha-2026-09-14.tgz", - "integrity": "sha512-CdpbZ03/Iq4HMth9RP2NHBv/gv9exDLkR3eJEDvS2fKdAjZSNYvL2PrJum3wHe8qOSFR8KzWuyKUx3AZ5vZc4w==", + "version": "1.64.0-alpha-2026-09-18", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.0-alpha-2026-09-18.tgz", + "integrity": "sha512-90N+GRyQZGvujyIxbSDt+3I0UoOw3M/Uq0cuJlWKsW9teGnc2DhXmNea9mIv78M0VmzHxH4o4dfjA30brJhGdw==", "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.64.0-alpha-2026-09-14" + "playwright-core": "1.64.0-alpha-2026-09-18" }, "bin": { "playwright": "cli.js" @@ -39,9 +39,9 @@ } }, "node_modules/playwright-core": { - "version": "1.64.0-alpha-2026-09-14", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.0-alpha-2026-09-14.tgz", - "integrity": "sha512-VCqlQ8lEKS8NAfEU/sWx3KP7gL2oCWd/DAGzPDZncGV7dUNNHpaVxHd2LWe2pc5y53h0Hqu3SWzRiPqKqkrYQA==", + "version": "1.64.0-alpha-2026-09-18", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.0-alpha-2026-09-18.tgz", + "integrity": "sha512-7BkKuCYyNBrVGtMKkcnUQqi+/Y8KO4oU395pTeCOqSUKVTjSXE/yXoE7ihLiO/JrU+vljL1hMmw/ArvuWOUPLQ==", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" diff --git a/tests/playwright-test/stable-test-runner/package.json b/tests/playwright-test/stable-test-runner/package.json index d02bb1f41e30e..2f0d6d30b1083 100644 --- a/tests/playwright-test/stable-test-runner/package.json +++ b/tests/playwright-test/stable-test-runner/package.json @@ -1,6 +1,6 @@ { "private": true, "dependencies": { - "@playwright/test": "^1.64.0-alpha-2026-09-14" + "@playwright/test": "^1.64.0-alpha-2026-09-18" } } diff --git a/utils/build/viteIstanbul.ts b/utils/build/viteIstanbul.ts new file mode 100644 index 0000000000000..0c8f09e378b12 --- /dev/null +++ b/utils/build/viteIstanbul.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createInstrumenter } from 'istanbul-lib-instrument'; + +import type { Plugin } from 'vite'; + +// Instruments the sources under packages/ before the other transforms, so that the +// counters point at the originals. +export function istanbul(): Plugin { + return { + name: 'playwright-istanbul', + enforce: 'pre', + transform(code, id) { + const file = id.split('?')[0]; + if (!/\/packages\/.*\.[jt]sx?$/.test(file) || file.endsWith('.d.ts') || file.includes('/node_modules/')) + return; + const parserPlugins: string[] = []; + if (/\.tsx?$/.test(file)) + parserPlugins.push('typescript'); + if (/\.[jt]sx$/.test(file)) + parserPlugins.push('jsx'); + const instrumenter = createInstrumenter({ esModules: true, produceSourceMap: true, parserPlugins }); + return { code: instrumenter.instrumentSync(code, file), map: { ...instrumenter.lastSourceMap(), version: 3 } }; + }, + }; +}