Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules/
/test-results/
/coverage/
/tests/coverage-report
.local-browsers/
/.dev_profile*
Expand Down
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const ignores = [
"*.js",
"**/.cache/",
"**/*.d.ts",
"coverage/",
"index.d.ts",
"node_modules/",
"output/",
Expand Down
82 changes: 82 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions packages/html-reporter/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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: {
Expand Down
31 changes: 1 addition & 30 deletions packages/injected/src/coverageScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
}
29 changes: 29 additions & 0 deletions packages/isomorphic/istanbulCoverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,32 @@ export function mergeIstanbulCoverage(into: Map<string, IstanbulFileCoverage>, 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;
}
2 changes: 2 additions & 0 deletions packages/isomorphic/trace/snapshotRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
18 changes: 18 additions & 0 deletions packages/trace-viewer/src/sw/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
* 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';

import { Progress, splitProgress } from './progress';
import { FetchTraceLoaderBackend, ZipTraceLoaderBackend } from './traceLoaderBackends';

import type { IstanbulCoverage } from '@isomorphic/istanbulCoverage';

type Client = {
id: string;
url: string;
Expand Down Expand Up @@ -152,6 +155,9 @@ async function doFetch(event: FetchEvent): Promise<Response> {
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;

Expand Down Expand Up @@ -233,6 +239,18 @@ async function doFetch(event: FetchEvent): Promise<Response> {
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');
Expand Down
4 changes: 3 additions & 1 deletion packages/trace-viewer/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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': {},
Expand Down
46 changes: 46 additions & 0 deletions tests/config/serviceWorkerCoverage.ts
Original file line number Diff line number Diff line change
@@ -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<any> };

// 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<any>(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(() => {});
}
Loading
Loading