如何在Nrwl Angular项目中实现Playwright覆盖率上报至SonarCloud
Nrwl Angular 项目中 Playwright 端到端测试覆盖率在 SonarCloud 展示的解决方案
核心思路
针对Nrwl单仓+Angular的场景,绕过babelrc配置限制,直接在Playwright测试流程中集成覆盖率收集逻辑,手动处理Angular编译后的source map转换,生成SonarCloud兼容的覆盖率格式。
具体配置步骤
1. 安装依赖包
npm install --save-dev @cypress/code-coverage istanbul-lib-coverage istanbul-lib-report istanbul-reports source-map ts-node
2. 配置Playwright覆盖率收集
在Playwright测试扩展文件(比如tests/global-setup.ts)中添加覆盖率收集逻辑:
import { test as base } from '@playwright/test'; import * as fs from 'fs'; import * as path from 'path'; import { createCoverageMap } from 'istanbul-lib-coverage'; export const test = base.extend({ context: async ({ context }, use) => { // 初始化覆盖率全局变量 await context.addInitScript(() => { (window as any).__coverage__ = {}; }); await use(context); // 测试结束后收集覆盖率数据 const coverage = await context.evaluate(() => (window as any).__coverage__); if (coverage) { const coverageMap = createCoverageMap(coverage); const outputDir = path.join(process.cwd(), 'coverage-playwright'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } fs.writeFileSync( path.join(outputDir, 'raw-coverage.json'), JSON.stringify(coverageMap.toJSON()) ); } }, }); export { expect } from '@playwright/test';
3. 编写Source Map转换脚本
创建scripts/process-coverage.ts,用于将编译后JS的覆盖率转换为原始TS文件的覆盖率:
import * as fs from 'fs'; import * as path from 'path'; import { createCoverageMap } from 'istanbul-lib-coverage'; import { SourceMapConsumer } from 'source-map'; async function transformCoverage() { const rawCoveragePath = path.join(process.cwd(), 'coverage-playwright', 'raw-coverage.json'); const rawCoverage = JSON.parse(fs.readFileSync(rawCoveragePath, 'utf-8')); const coverageMap = createCoverageMap(rawCoverage); // 遍历所有JS文件,通过source map映射到TS源文件 for (const [jsFilePath, jsCoverage] of Object.entries(coverageMap.data)) { if (!jsFilePath.endsWith('.js')) continue; const sourceMapPath = `${jsFilePath}.map`; if (!fs.existsSync(sourceMapPath)) continue; const sourceMapContent = JSON.parse(fs.readFileSync(sourceMapPath, 'utf-8')); const consumer = await new SourceMapConsumer(sourceMapContent); const tsSourcePath = path.join(consumer.sourceRoot || '', consumer.sources[0]); // 创建TS文件的覆盖率结构 const tsCoverage = { ...jsCoverage, path: tsSourcePath, s: {}, // 存储转换后的语句覆盖率 f: {}, // 存储转换后的函数覆盖率 b: {} // 存储转换后的分支覆盖率 }; // 转换语句覆盖率 for (const [jsLine, count] of Object.entries(jsCoverage.s)) { const originalPos = consumer.originalPositionFor({ line: parseInt(jsLine), column: 0 }); if (originalPos.line) { tsCoverage.s[originalPos.line] = count; } } // 移除JS文件覆盖率,添加TS文件覆盖率 coverageMap.removeFile(jsFilePath); coverageMap.addFileCoverage(tsCoverage); await consumer.destroy(); } // 生成SonarCloud支持的lcov格式报告 const reporter = require('istanbul-reports').create('lcov'); await reporter.execute({ coverageMap, sourceFinder: (filePath: string) => fs.readFileSync(filePath, 'utf-8') }); // 将lcov报告移动到指定目录 const generatedLcovPath = path.join(process.cwd(), 'lcov.info'); const targetLcovPath = path.join(process.cwd(), 'coverage-playwright', 'lcov.info'); if (fs.existsSync(generatedLcovPath)) { fs.renameSync(generatedLcovPath, targetLcovPath); } } transformCoverage().catch(err => { console.error('覆盖率转换失败:', err); process.exit(1); });
4. 配置Nrwl运行命令
在nx.json或项目的project.json中添加集成命令:
{ "targets": { "e2e-with-coverage": { "executor": "nx:run-commands", "options": { "commands": [ "npx playwright test --config=playwright.config.ts", "npx ts-node scripts/process-coverage.ts" ], "parallel": false } } } }
5. SonarCloud配置
在项目根目录的sonar-project.properties中添加覆盖率路径:
sonar.typescript.lcov.reportPaths=coverage-playwright/lcov.info sonar.exclusions=**/*.js,**/*.map,**/node_modules/**
执行流程
运行以下命令完成测试与覆盖率收集:
nx run your-project-name:e2e-with-coverage
之后将代码推送到仓库,SonarCloud会自动读取转换后的覆盖率数据并展示。
内容的提问来源于stack exchange,提问作者Serje Strelov
相关产品推荐
相关产品推荐

