playwright-coverage icon indicating copy to clipboard operation
playwright-coverage copied to clipboard

Reporter hangs on convertToIstanbulCoverage

Open sbaidon opened this issue 10 months ago • 0 comments

After completion of tests the reporter hangs and ultimately never creates a report coverage, after doing a bit of debugging this seems to be happening on data.ts, specifically the convertToIstanbulCoverage function when calling v8ToIstanbul.



export async function convertToIstanbulCoverage(
  v8Coverage: ProcessCov,
  sources: ReadonlyMap<string, string>,
  sourceMaps: ReadonlyMap<string, EncodedSourceMap | undefined>,
  exclude: readonly string[],
  sourceRoot: string,
) {
  const istanbulCoverage = createCoverageMap({});

  for (const script of v8Coverage.result) {
    const source = sources.get(script.url);
    const sourceMap = sourceMaps.get(script.url);

    if (source == null || !sourceMap?.mappings) {
      continue;
    }

    function sanitizePath(path: string) {
      let url;

      try {
        url = new URL(path);
      } catch {
        url = pathToFileURL(path);
      }

      let relativePath;
      if (url.protocol.startsWith('webpack')) {
        relativePath = url.pathname.slice(1); // webpack: URLs contain relative paths
      } else {
        relativePath = url.pathname;
      }

      if (relativePath.includes('/webpack:/')) {
        // v8ToIstanbul breaks when the source root in the source map is set to webpack:
        // It treats the URL as a path, leading to a confusing result.
        relativePath = relativePath.slice(
          relativePath.indexOf('/webpack:/') + '/webpack:/'.length,
        );
      } else if (posix.isAbsolute(relativePath)) {
        relativePath = posix.relative(pathToFileURL(sourceRoot).pathname, path);
      }

      return relativePath;
    }

    const isExcludedCache = new Map<string, boolean>();
    const convertor = v8ToIstanbul(
      // This path is used to resolve files, but the filename doesn't matter
      join(sourceRoot, 'unused.js'),
      0,
      sourceMap?.mappings
        ? {
            source,
            sourceMap: {sourcemap: sourceMap},
          }
        : {
            source: convertSourceMap.removeMapFileComments(
              convertSourceMap.removeComments(source),
            ),
          },
      path => {
        let isExcluded = isExcludedCache.get(path);

        if (isExcluded != null) {
          return isExcluded;
        }

        const relativePath = sanitizePath(path);

        isExcluded =
          // ignore files outside of the root
          relativePath.startsWith('../') ||
          // ignore webpack files
          path.includes('/webpack:/webpack/') ||
          relativePath === 'webpack/bootstrap' ||
          relativePath.startsWith('webpack/runtime/') ||
          // ignore dependencies
          relativePath.startsWith('node_modules/') ||
          relativePath.includes('/node_modules/') ||
          // apply exclusions
          isMatch(relativePath, exclude);
        isExcludedCache.set(path, isExcluded);

        return isExcluded;
      },
    );

    try {
      await convertor.load();
    } catch (error) {
      continue;
    }

    convertor.applyCoverage(script.functions);

    istanbulCoverage.merge(
      Object.fromEntries(
        Array.from(
          Object.entries(convertor.toIstanbul()),
          ([path, coverage]) => {
            path = sanitizePath(path);
            return [
              path,
              {
                ...coverage,
                path,
              },
            ] as const;
          },
        ),
      ),
    );
  }

  return istanbulCoverage;
}

Playwright Config


export const playwrightBaseConfig = {
    testDir: './tests',
    /* Run tests in files in parallel */
    fullyParallel: true,
    /* Fail the build on CI if you accidentally left test.only in the source code. */
    forbidOnly: !!process.env.CI,
    /* Retry on CI only */
    retries: process.env.CI ? 2 : 1,
    /* Opt out of parallel tests on CI. */
    workers: process.env.CI ? 4 : undefined,
    /* Reporter to use. See https://playwright.dev/docs/test-reporters */
    reporter: [
        ['dot'],
        [
            '@bgotink/playwright-coverage',
            defineCoverageReporterConfig({
                sourceRoot: path.join(__dirname, 'src/'),
                exclude: ['**/dist/**', '**/tests/**'],
                resultDir: path.join(
                    __dirname,
                    '../../components/cross-flow-frontend/coverage/e2e',
                ),
                reports: [
                    [
                        'json',
                        {
                            file: 'coverage-final.json',
                        },
                    ],
                    ['html'],
                ],
                watermarks: {},
            }),
        ],
    ],
    /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
    use: {
        /* Base URL to use in actions like `await page.goto('/')`. */
        baseURL: 'http://127.0.0.1:3333',

        /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
        trace: 'on-first-retry',
        /* Take a screenshot of the test only on failure */
        screenshot: process.env.CI ? 'only-on-failure' : 'on',
    },
    snapshotPathTemplate:
        '{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-{projectName}{ext}',

    /* Configure projects for major browsers */
    projects: [
        {
            name: 'chromium',
            use: {
                ...devices['Desktop Chrome'],
                channel: 'chromium',
                viewport: { width: 1280, height: 720 },
            },
        },
        {
            name: 'firefox',
            use: {
                ...devices['Desktop Firefox'],
                viewport: { width: 1280, height: 720 },
            },
        },
        {
            name: 'webkit',
            use: {
                ...devices['Desktop Safari'],
                viewport: { width: 1280, height: 720 },
            },
        },

        /* Test against mobile viewports. */
        // {
        //   name: 'Mobile Chrome',
        //   use: { ...devices['Pixel 5'] },
        // },
        // {
        //   name: 'Mobile Safari',
        //   use: { ...devices['iPhone 12'] },
        // },

        /* Test against branded browsers. */
        // {
        //   name: 'Microsoft Edge',
        //   use: { ...devices['Desktop Edge'], channel: 'msedge' },
        // },
        // {
        //   name: 'Google Chrome',
        //   use: { ...devices['Desktop Chrome'], channel: 'chrome' },
        // },
    ],

    /* Set consistent viewport size for all tests */
    /* Run your local dev server before starting the tests */
    webServer: {
        command: 'CI' in process.env ? 'yarn start:local-unmocked' : 'yarn start:local-unmocked',
        url: 'http://127.0.0.1:3333',
        reuseExistingServer: !process.env.CI,
    },
} satisfies PlaywrightTestConfig;

export default defineConfig(playwrightBaseConfig);

This was tested on different versions of @playwright/test with the same results:

  • 1.51.0
  • 1.49.0

I will try to setup a repro for you unfortunately I cannot share the codebase we work on.

sbaidon avatar Apr 03 '25 17:04 sbaidon