@arcadialdev/arcality 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
  2. package/.agents/skills/frontend-design/LICENSE.txt +177 -0
  3. package/.agents/skills/frontend-design/SKILL.md +42 -0
  4. package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
  5. package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
  6. package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
  7. package/.agents/skills/playwright-best-practices/README.md +147 -0
  8. package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
  9. package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
  10. package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
  11. package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
  12. package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
  13. package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
  14. package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
  15. package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
  16. package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
  17. package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
  18. package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
  19. package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
  20. package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
  21. package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
  22. package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
  23. package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
  24. package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
  25. package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
  26. package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
  27. package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
  28. package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
  29. package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
  30. package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
  31. package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
  32. package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
  33. package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
  34. package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
  35. package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
  36. package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
  37. package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
  38. package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
  39. package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
  40. package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
  41. package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
  42. package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
  43. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
  44. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
  45. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
  46. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
  47. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
  48. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
  49. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
  50. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
  51. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
  52. package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
  53. package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
  54. package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
  55. package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
  56. package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
  57. package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
  58. package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
  59. package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
  60. package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
  61. package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
  62. package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
  63. package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
  64. package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
  65. package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
  66. package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
  67. package/.env.example +21 -0
  68. package/README.md +30 -0
  69. package/bin/arcality.mjs +86 -0
  70. package/package.json +66 -0
  71. package/playwright.config.ts +12 -0
  72. package/scripts/cleanup-qmsdev.mjs +63 -0
  73. package/scripts/discover-view.mjs +52 -0
  74. package/scripts/extract-view.mjs +64 -0
  75. package/scripts/gen-and-run.mjs +838 -0
  76. package/scripts/init.mjs +290 -0
  77. package/scripts/migrate-to-central-out.mjs +157 -0
  78. package/scripts/postinstall.mjs +63 -0
  79. package/scripts/rebrand-report.mjs +241 -0
  80. package/scripts/setup.mjs +166 -0
  81. package/src/KnowledgeService.ts +239 -0
  82. package/src/arcalityClient.mjs +266 -0
  83. package/src/configLoader.mjs +179 -0
  84. package/src/configManager.mjs +172 -0
  85. package/src/consoleBanner.ts +32 -0
  86. package/src/envSetup.ts +205 -0
  87. package/src/index.ts +25 -0
  88. package/src/projectInspector.ts +42 -0
  89. package/src/services/collectiveMemoryService.ts +178 -0
  90. package/src/testRunner.ts +201 -0
  91. package/tests/_helpers/ArcalityReporter.ts +490 -0
  92. package/tests/_helpers/agentic-runner.spec.ts +741 -0
  93. package/tests/_helpers/ai-agent-helper.ts +1573 -0
  94. package/tests/_helpers/discover-view.spec.ts +238 -0
  95. package/tests/_helpers/extract-view.spec.ts +118 -0
  96. package/tests/_helpers/qa-tools.ts +333 -0
  97. package/tests/_helpers/smart-action.spec.ts +1458 -0
@@ -0,0 +1,497 @@
1
+ # Test Coverage
2
+
3
+ ## Table of Contents
4
+
5
+ 1. [Coverage Setup](#coverage-setup)
6
+ 2. [Collecting Coverage](#collecting-coverage)
7
+ 3. [Coverage Reports](#coverage-reports)
8
+ 4. [Coverage Thresholds](#coverage-thresholds)
9
+ 5. [Advanced Patterns](#advanced-patterns)
10
+ 6. [CI Integration](#ci-integration)
11
+
12
+ ## Coverage Setup
13
+
14
+ ### Install Dependencies
15
+
16
+ ```bash
17
+ # For V8 coverage (built into Playwright)
18
+ # No additional dependencies needed
19
+
20
+ # For Istanbul-based coverage (more features)
21
+ npm install -D nyc @istanbuljs/nyc-config-typescript
22
+ ```
23
+
24
+ ### Basic Configuration
25
+
26
+ ```typescript
27
+ // playwright.config.ts
28
+ import { defineConfig } from "@playwright/test";
29
+
30
+ export default defineConfig({
31
+ use: {
32
+ // Enable coverage collection
33
+ contextOptions: {
34
+ // V8 coverage is automatic with the API below
35
+ },
36
+ },
37
+ });
38
+ ```
39
+
40
+ ### V8 Coverage Fixture
41
+
42
+ ```typescript
43
+ // fixtures/coverage.ts
44
+ import { test as base, expect } from "@playwright/test";
45
+ import fs from "fs";
46
+ import path from "path";
47
+ import { randomUUID } from "crypto";
48
+
49
+ export const test = base.extend<{}, { collectCoverage: void }>({
50
+ collectCoverage: [
51
+ async ({ browser }, use) => {
52
+ // Start coverage for all pages
53
+ const context = await browser.newContext();
54
+ const page = await context.newPage();
55
+
56
+ await page.coverage.startJSCoverage();
57
+ await page.coverage.startCSSCoverage();
58
+
59
+ await use();
60
+
61
+ // Collect coverage
62
+ const [jsCoverage, cssCoverage] = await Promise.all([
63
+ page.coverage.stopJSCoverage(),
64
+ page.coverage.stopCSSCoverage(),
65
+ ]);
66
+
67
+ // Save coverage data
68
+ const coverageDir = "./coverage";
69
+ if (!fs.existsSync(coverageDir)) {
70
+ fs.mkdirSync(coverageDir, { recursive: true });
71
+ }
72
+
73
+ fs.writeFileSync(
74
+ path.join(coverageDir, `coverage-${randomUUID()}.json`),
75
+ JSON.stringify([...jsCoverage, ...cssCoverage])
76
+ );
77
+
78
+ await context.close();
79
+ },
80
+ { scope: "worker", auto: true },
81
+ ],
82
+ });
83
+ ```
84
+
85
+ ## Collecting Coverage
86
+
87
+ ### Per-Test Coverage
88
+
89
+ ```typescript
90
+ test("collect coverage for single test", async ({ page }) => {
91
+ // Start coverage collection
92
+ await page.coverage.startJSCoverage({
93
+ resetOnNavigation: false,
94
+ });
95
+
96
+ // Run test
97
+ await page.goto("/app");
98
+ await page.getByRole("button", { name: "Submit" }).click();
99
+ await expect(page.getByText("Success")).toBeVisible();
100
+
101
+ // Stop and get coverage
102
+ const coverage = await page.coverage.stopJSCoverage();
103
+
104
+ // Filter to only your source files
105
+ const appCoverage = coverage.filter((entry) => entry.url.includes("/src/"));
106
+
107
+ console.log(`Covered ${appCoverage.length} source files`);
108
+ });
109
+ ```
110
+
111
+ ### Coverage for Specific Files
112
+
113
+ ```typescript
114
+ test("track specific module coverage", async ({ page }) => {
115
+ await page.coverage.startJSCoverage();
116
+
117
+ await page.goto("/checkout");
118
+ await page.getByRole("button", { name: "Pay" }).click();
119
+
120
+ const coverage = await page.coverage.stopJSCoverage();
121
+
122
+ // Find coverage for checkout module
123
+ const checkoutCoverage = coverage.find((c) => c.url.includes("checkout.js"));
124
+
125
+ if (checkoutCoverage) {
126
+ const totalBytes = checkoutCoverage.text?.length || 0;
127
+ const coveredBytes = checkoutCoverage.ranges.reduce(
128
+ (sum, range) => sum + (range.end - range.start),
129
+ 0
130
+ );
131
+ const percentage = (coveredBytes / totalBytes) * 100;
132
+
133
+ console.log(`Checkout module: ${percentage.toFixed(1)}% covered`);
134
+ expect(percentage).toBeGreaterThan(80);
135
+ }
136
+ });
137
+ ```
138
+
139
+ ### CSS Coverage
140
+
141
+ ```typescript
142
+ test("collect CSS coverage", async ({ page }) => {
143
+ await page.coverage.startCSSCoverage();
144
+
145
+ await page.goto("/app");
146
+
147
+ // Interact to trigger different CSS states
148
+ await page.getByRole("button").hover();
149
+ await page.getByRole("dialog").waitFor();
150
+
151
+ const cssCoverage = await page.coverage.stopCSSCoverage();
152
+
153
+ // Find unused CSS
154
+ for (const entry of cssCoverage) {
155
+ const totalBytes = entry.text?.length || 0;
156
+ const usedBytes = entry.ranges.reduce(
157
+ (sum, range) => sum + (range.end - range.start),
158
+ 0
159
+ );
160
+ const unusedPercentage = ((totalBytes - usedBytes) / totalBytes) * 100;
161
+
162
+ if (unusedPercentage > 50) {
163
+ console.warn(`${entry.url}: ${unusedPercentage.toFixed(1)}% unused CSS`);
164
+ }
165
+ }
166
+ });
167
+ ```
168
+
169
+ ## Coverage Reports
170
+
171
+ ### Converting to Istanbul Format
172
+
173
+ ```typescript
174
+ // scripts/convert-coverage.ts
175
+ import { execSync } from "child_process";
176
+ import fs from "fs";
177
+ import path from "path";
178
+ import v8ToIstanbul from "v8-to-istanbul";
179
+
180
+ async function convertCoverage() {
181
+ const coverageDir = "./coverage";
182
+ const files = fs.readdirSync(coverageDir).filter((f) => f.endsWith(".json"));
183
+
184
+ const istanbulCoverage: any = {};
185
+
186
+ for (const file of files) {
187
+ const coverageData = JSON.parse(
188
+ fs.readFileSync(path.join(coverageDir, file), "utf-8")
189
+ );
190
+
191
+ for (const entry of coverageData) {
192
+ if (!entry.url.startsWith("file://")) continue;
193
+
194
+ const filePath = entry.url.replace("file://", "");
195
+ const converter = v8ToIstanbul(filePath);
196
+
197
+ await converter.load();
198
+ converter.applyCoverage(entry.functions || []);
199
+
200
+ const istanbul = converter.toIstanbul();
201
+ Object.assign(istanbulCoverage, istanbul);
202
+ }
203
+ }
204
+
205
+ fs.writeFileSync(
206
+ path.join(coverageDir, "coverage-final.json"),
207
+ JSON.stringify(istanbulCoverage)
208
+ );
209
+ }
210
+
211
+ convertCoverage();
212
+ ```
213
+
214
+ ### Generating HTML Report
215
+
216
+ ```bash
217
+ # Using nyc to generate report
218
+ npx nyc report --reporter=html --reporter=text --temp-dir=./coverage
219
+ ```
220
+
221
+ ```typescript
222
+ // package.json scripts
223
+ {
224
+ "scripts": {
225
+ "test": "playwright test",
226
+ "test:coverage": "playwright test && npm run coverage:report",
227
+ "coverage:report": "npx nyc report --reporter=html --reporter=lcov --temp-dir=./coverage"
228
+ }
229
+ }
230
+ ```
231
+
232
+ ### Custom Coverage Reporter
233
+
234
+ ```typescript
235
+ // reporters/coverage-reporter.ts
236
+ import type { Reporter, FullResult } from "@playwright/test/reporter";
237
+ import fs from "fs";
238
+ import path from "path";
239
+
240
+ class CoverageReporter implements Reporter {
241
+ private coverageData: any[] = [];
242
+
243
+ onEnd(result: FullResult) {
244
+ // Aggregate all coverage files
245
+ const coverageDir = "./coverage";
246
+ const files = fs
247
+ .readdirSync(coverageDir)
248
+ .filter((f) => f.endsWith(".json"));
249
+
250
+ for (const file of files) {
251
+ const data = JSON.parse(
252
+ fs.readFileSync(path.join(coverageDir, file), "utf-8")
253
+ );
254
+ this.coverageData.push(...data);
255
+ }
256
+
257
+ // Generate summary
258
+ const summary = this.generateSummary();
259
+ console.log("\n📊 Coverage Summary:");
260
+ console.log(` Files: ${summary.totalFiles}`);
261
+ console.log(` Lines: ${summary.lineCoverage.toFixed(1)}%`);
262
+ console.log(` Bytes: ${summary.byteCoverage.toFixed(1)}%`);
263
+
264
+ if (summary.lineCoverage < 80) {
265
+ console.warn("⚠️ Coverage below 80% threshold!");
266
+ }
267
+ }
268
+
269
+ private generateSummary() {
270
+ let totalBytes = 0;
271
+ let coveredBytes = 0;
272
+ const files = new Set<string>();
273
+
274
+ for (const entry of this.coverageData) {
275
+ if (entry.url.includes("/src/")) {
276
+ files.add(entry.url);
277
+ totalBytes += entry.text?.length || 0;
278
+ coveredBytes += entry.ranges.reduce(
279
+ (sum: number, r: any) => sum + (r.end - r.start),
280
+ 0
281
+ );
282
+ }
283
+ }
284
+
285
+ return {
286
+ totalFiles: files.size,
287
+ byteCoverage: (coveredBytes / totalBytes) * 100,
288
+ lineCoverage: (coveredBytes / totalBytes) * 100, // Simplified
289
+ };
290
+ }
291
+ }
292
+
293
+ export default CoverageReporter;
294
+ ```
295
+
296
+ ## Coverage Thresholds
297
+
298
+ ### Enforcing Minimum Coverage
299
+
300
+ ```typescript
301
+ // tests/coverage.spec.ts
302
+ import { test, expect } from "@playwright/test";
303
+ import fs from "fs";
304
+ import path from "path";
305
+
306
+ test.afterAll(async () => {
307
+ const coverageDir = "./coverage";
308
+ const files = fs.readdirSync(coverageDir).filter((f) => f.endsWith(".json"));
309
+
310
+ let totalBytes = 0;
311
+ let coveredBytes = 0;
312
+
313
+ for (const file of files) {
314
+ const coverage = JSON.parse(
315
+ fs.readFileSync(path.join(coverageDir, file), "utf-8")
316
+ );
317
+
318
+ for (const entry of coverage) {
319
+ if (!entry.url.includes("/src/")) continue;
320
+ totalBytes += entry.text?.length || 0;
321
+ coveredBytes += entry.ranges.reduce(
322
+ (sum: number, r: any) => sum + (r.end - r.start),
323
+ 0
324
+ );
325
+ }
326
+ }
327
+
328
+ const coveragePercent = (coveredBytes / totalBytes) * 100;
329
+
330
+ // Enforce threshold
331
+ expect(coveragePercent).toBeGreaterThan(80);
332
+ });
333
+ ```
334
+
335
+ ### Per-Directory Thresholds
336
+
337
+ ```typescript
338
+ // coverage-check.ts
339
+ interface CoverageThreshold {
340
+ pattern: RegExp;
341
+ minCoverage: number;
342
+ }
343
+
344
+ const thresholds: CoverageThreshold[] = [
345
+ { pattern: /\/src\/core\//, minCoverage: 90 },
346
+ { pattern: /\/src\/utils\//, minCoverage: 85 },
347
+ { pattern: /\/src\/components\//, minCoverage: 70 },
348
+ { pattern: /\/src\/pages\//, minCoverage: 60 },
349
+ ];
350
+
351
+ function checkThresholds(coverage: any[]): string[] {
352
+ const violations: string[] = [];
353
+
354
+ for (const threshold of thresholds) {
355
+ const matchingFiles = coverage.filter((c) => threshold.pattern.test(c.url));
356
+
357
+ let total = 0;
358
+ let covered = 0;
359
+
360
+ for (const file of matchingFiles) {
361
+ total += file.text?.length || 0;
362
+ covered += file.ranges.reduce(
363
+ (sum: number, r: any) => sum + (r.end - r.start),
364
+ 0
365
+ );
366
+ }
367
+
368
+ const percent = total > 0 ? (covered / total) * 100 : 0;
369
+
370
+ if (percent < threshold.minCoverage) {
371
+ violations.push(
372
+ `${threshold.pattern}: ${percent.toFixed(1)}% < ${
373
+ threshold.minCoverage
374
+ }%`
375
+ );
376
+ }
377
+ }
378
+
379
+ return violations;
380
+ }
381
+ ```
382
+
383
+ ## Advanced Patterns
384
+
385
+ ### Merging Coverage Across Shards
386
+
387
+ ```typescript
388
+ // scripts/merge-coverage.ts
389
+ import fs from "fs";
390
+ import { glob } from "glob";
391
+
392
+ async function mergeCoverage() {
393
+ const files = await glob("shard-*/coverage/*.json");
394
+ const merged = new Map<string, any>();
395
+
396
+ for (const file of files) {
397
+ const data = JSON.parse(fs.readFileSync(file, "utf-8"));
398
+ for (const entry of data) {
399
+ if (merged.has(entry.url)) {
400
+ const existing = merged.get(entry.url);
401
+ existing.ranges.push(...entry.ranges);
402
+ } else {
403
+ merged.set(entry.url, { ...entry });
404
+ }
405
+ }
406
+ }
407
+
408
+ fs.writeFileSync(
409
+ "./coverage/merged.json",
410
+ JSON.stringify([...merged.values()])
411
+ );
412
+ }
413
+
414
+ mergeCoverage();
415
+ ```
416
+
417
+ ### Incremental Coverage
418
+
419
+ ```typescript
420
+ // Check coverage only for changed files in CI
421
+ import { execSync } from "child_process";
422
+ import fs from "fs";
423
+
424
+ const changedFiles = execSync("git diff --name-only HEAD~1")
425
+ .toString()
426
+ .split("\n")
427
+ .filter((f) => f.endsWith(".ts"));
428
+
429
+ const coverage = JSON.parse(fs.readFileSync("./coverage/merged.json", "utf-8"));
430
+
431
+ for (const file of changedFiles) {
432
+ const entry = coverage.find((c: any) => c.url.includes(file));
433
+ if (entry) {
434
+ const percent =
435
+ (entry.ranges.reduce((s: number, r: any) => s + r.end - r.start, 0) /
436
+ (entry.text?.length || 1)) *
437
+ 100;
438
+ console.log(`${file}: ${percent.toFixed(1)}%`);
439
+ }
440
+ }
441
+ ```
442
+
443
+ ## CI Integration
444
+
445
+ ### GitHub Actions
446
+
447
+ ```yaml
448
+ # .github/workflows/test.yml
449
+ name: Tests with Coverage
450
+
451
+ on: [push, pull_request]
452
+
453
+ jobs:
454
+ test:
455
+ runs-on: ubuntu-latest
456
+ steps:
457
+ - uses: actions/checkout@v4
458
+
459
+ - uses: actions/setup-node@v4
460
+ with:
461
+ node-version: 22
462
+
463
+ - run: npm ci
464
+ - run: npx playwright install --with-deps
465
+
466
+ - name: Run tests with coverage
467
+ run: npm run test:coverage
468
+
469
+ - name: Upload coverage to Codecov
470
+ uses: codecov/codecov-action@v3
471
+ with:
472
+ files: ./coverage/lcov.info
473
+ fail_ci_if_error: true
474
+
475
+ - name: Check coverage threshold
476
+ run: |
477
+ COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
478
+ if (( $(echo "$COVERAGE < 80" | bc -l) )); then
479
+ echo "Coverage $COVERAGE% is below 80% threshold"
480
+ exit 1
481
+ fi
482
+ ```
483
+
484
+ ## Anti-Patterns to Avoid
485
+
486
+ | Anti-Pattern | Problem | Solution |
487
+ | ---------------------------- | -------------------------------------- | --------------------------- |
488
+ | Coverage for coverage's sake | Gaming metrics | Focus on critical paths |
489
+ | 100% coverage target | Diminishing returns, tests for getters | Set realistic thresholds |
490
+ | Ignoring coverage drops | Technical debt | Enforce thresholds in CI |
491
+ | No source map support | Wrong line numbers | Enable source maps in build |
492
+ | Coverage only in CI | Late feedback | Run locally too |
493
+
494
+ ## Related References
495
+
496
+ - **CI/CD**: See [ci-cd.md](ci-cd.md) for pipeline configuration
497
+ - **Performance**: See [performance.md](performance.md) for optimizing coverage collection