Skip to content
Open
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
11 changes: 9 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ jobs:
# Skip unnecessary optimizations for test builds
SKIP_ENV_VALIDATION: true

- name: Run integration tests
- name: Run integration tests with coverage
working-directory: ./ui
run: pnpm run test:integration
run: pnpm run test:integration:coverage
env:
# Use production build for faster startup
NODE_ENV: production
Expand All @@ -159,6 +159,13 @@ jobs:
path: ui/test-results/
retention-days: 3

- name: Upload UI coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ui/coverage-playwright/lcov.info
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}

sdk-ts-ci:
runs-on: ubuntu-latest
env:
Expand Down
2 changes: 2 additions & 0 deletions ui/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ next-env.d.ts

CLAUDE.md
.claude

playwright-coverage/
11 changes: 10 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"typecheck": "tsc --noEmit",
"fetch-api-types": "openapi-typescript http://localhost:8000/openapi.json -o src/core/api/generated/api-types.ts",
"test:integration": "playwright test",
"test:integration:coverage": "playwright test && node scripts/coverage-playwright.cjs",
"test:integration:ui": "playwright test --ui",
"test:integration:headed": "playwright test --headed",
"test:integration:debug": "playwright test --debug",
Expand Down Expand Up @@ -75,13 +76,21 @@
"eslint-config-next": "16.1.1",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.3.0",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.2.0",
"npm-scripts-info": "^0.3.9",
"openapi-typescript": "^7.10.1",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.1.7",
"postcss-preset-mantine": "1.17.0",
"postcss-simple-vars": "7.0.1",
"prettier": "^3.4.2",
"tailwindcss": "^4",
"v8-to-istanbul": "^9.3.0",
"typescript": "^5",
"typescript-eslint": "^8.32.1"
"typescript-eslint": "^8.32.1",
"v8-to-istanbul": "^9.3.0"
}
}
65 changes: 65 additions & 0 deletions ui/pnpm-lock.yaml

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

142 changes: 142 additions & 0 deletions ui/scripts/coverage-playwright.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// Post-process Playwright raw V8 coverage into an Istanbul HTML report.
//
// Input:
// - JSON files under playwright-coverage/raw/<project>/*.json
// (emitted by the shared Playwright fixture in tests/fixtures.ts)
//
// Output:
// - Istanbul HTML + text-summary under coverage-playwright/
// - LCOV file (lcov.info) under coverage-playwright/ for Codecov

const fs = require('node:fs/promises');
const path = require('node:path');

const v8ToIstanbul = require('v8-to-istanbul');
const istanbulLibCoverage = require('istanbul-lib-coverage');
const istanbulLibReport = require('istanbul-lib-report');
const istanbulReports = require('istanbul-reports');

async function listJsonFiles(dir) {
const files = [];
async function walk(current) {
let entries;
try {
entries = await fs.readdir(current, { withFileTypes: true });
} catch (err) {
if (err && err.code === 'ENOENT') return;
throw err;
}

for (const entry of entries) {
const fullPath = path.join(current, entry.name);
if (entry.isDirectory()) {
await walk(fullPath);
} else if (entry.isFile() && entry.name.endsWith('.json')) {
files.push(fullPath);
}
}
}

await walk(dir);
return files;
}

async function main() {
const cwd = process.cwd();
const rawRoot = path.join(cwd, 'playwright-coverage', 'raw');

const files = await listJsonFiles(rawRoot);
if (files.length === 0) {
// eslint-disable-next-line no-console
console.warn(
'[coverage-playwright] No raw coverage files found under',
rawRoot
);
return;
}

const coverageMap = istanbulLibCoverage.createCoverageMap({});

for (const file of files) {
// eslint-disable-next-line no-console
console.log('[coverage-playwright] Processing', path.relative(cwd, file));
const jsonText = await fs.readFile(file, 'utf-8');
/** @type {Array<{ url?: string; source?: string; functions: any[] }>} */
const entries = JSON.parse(jsonText);

for (const entry of entries) {
if (!entry || !entry.source || !entry.functions) continue;

// Use the URL path (if present) as a pseudo file path in reports.
let virtualPath = entry.url || 'anonymous-script.js';
try {
if (virtualPath.startsWith('http://') || virtualPath.startsWith('https://')) {
const u = new URL(virtualPath);
virtualPath = u.pathname || virtualPath;
}
} catch {
// Keep original value if URL parsing fails
}

const converter = v8ToIstanbul(virtualPath, 0, {
source: entry.source,
});
await converter.load();
await converter.applyCoverage(entry.functions);

const fileCoverage = converter.toIstanbul();

// Only keep coverage for UI application sources under src/.
// This filters out Next.js internals and node_modules noise.
const filtered = istanbulLibCoverage.createCoverageMap({});
for (const filePath of Object.keys(fileCoverage)) {
if (
filePath.includes('/src/') ||
filePath.includes('\\src\\') ||
filePath.startsWith('src/')
) {
filtered.addFileCoverage(fileCoverage[filePath]);
}
}

coverageMap.merge(filtered);
}
}

const outDir = path.join(cwd, 'coverage-playwright');
await fs.mkdir(outDir, { recursive: true });

const context = istanbulLibReport.createContext({
dir: outDir,
coverageMap,
});

const reports = [
istanbulReports.create('html'),
istanbulReports.create('text-summary'),
// LCOV format that Codecov, Coveralls, etc. understand.
istanbulReports.create('lcovonly', { file: 'lcov.info' }),
];

for (const report of reports) {
report.execute(context);
}

// eslint-disable-next-line no-console
console.log(
'[coverage-playwright] HTML report written to',
path.join('coverage-playwright', 'index.html')
);
console.log(
'[coverage-playwright] LCOV report written to',
path.join('coverage-playwright', 'lcov.info')
);
}

main().catch((err) => {
// eslint-disable-next-line no-console
console.error('[coverage-playwright] Failed:', err);
process.exitCode = 1;
});

Loading
Loading