Files
n8n/packages/testing/playwright/scripts/fetch-currents-metrics.mjs
alighasami 3d5eaf9445
Some checks failed
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
first commit
2026-03-17 16:22:57 +03:30

144 lines
4.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Fetches test duration metrics from Currents API for test orchestration.
*
* Usage:
* CURRENTS_API_KEY=<key> node packages/testing/playwright/scripts/fetch-currents-metrics.mjs --project=<id>
*
* Output: .github/test-metrics/playwright.json
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { execSync } from 'child_process';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT_DIR = path.resolve(__dirname, '../../../..');
const PLAYWRIGHT_DIR = path.join(ROOT_DIR, 'packages', 'testing', 'playwright');
const OUTPUT_PATH = path.join(ROOT_DIR, '.github', 'test-metrics', 'playwright.json');
const CURRENTS_API = 'https://api.currents.dev/v1';
const DEFAULT_DURATION = 60000; // 1 minute default for new specs (accounts for container startup)
const PROJECT_ID = process.argv.find((a) => a.startsWith('--project='))?.split('=')[1];
if (!PROJECT_ID) {
console.error('Usage: CURRENTS_API_KEY=<key> node fetch-currents-metrics.mjs --project=<id>');
process.exit(1);
}
async function fetchSpecPerformance(apiKey) {
const specs = [];
let page = 0;
let hasMore = true;
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
while (hasMore && page < 100) {
const url = new URL(`${CURRENTS_API}/spec-files/${PROJECT_ID}`);
url.searchParams.set('limit', '50');
url.searchParams.set('page', page.toString());
url.searchParams.set('date_start', sevenDaysAgo.toISOString());
url.searchParams.set('date_end', new Date().toISOString());
const res = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` },
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
const data = await res.json();
// Filter to e2e specs only
const e2eSpecs = data.data.list.filter((s) => s.spec.startsWith('tests/e2e/'));
specs.push(...e2eSpecs);
hasMore = data.data.nextPage !== false;
page++;
process.stdout.write(`\rFetched ${specs.length} e2e specs...`);
}
console.log();
return specs;
}
function getPlaywrightSpecs() {
console.log('Getting specs from Playwright...');
try {
const output = execSync('pnpm playwright test --list --project="multi-main:e2e"', {
cwd: PLAYWRIGHT_DIR,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
});
const specs = new Set([...output.matchAll(/ (tests\/e2e\/[^\s:]+\.spec\.ts)/g)].map((m) => m[1]));
console.log(`Found ${specs.size} specs in Playwright`);
return specs;
} catch (e) {
console.warn('Warning: Could not get Playwright specs, skipping validation');
return null;
}
}
async function main() {
const apiKey = process.env.CURRENTS_API_KEY;
if (!apiKey) {
console.error('CURRENTS_API_KEY required');
process.exit(1);
}
const validSpecs = getPlaywrightSpecs();
const specMetrics = await fetchSpecPerformance(apiKey);
const output = {
updatedAt: new Date().toISOString(),
source: 'currents',
projectId: PROJECT_ID,
specs: {},
};
const staleSpecs = [];
for (const item of specMetrics) {
// Skip specs not in Playwright (deleted/isolated)
if (validSpecs && !validSpecs.has(item.spec)) {
staleSpecs.push(item.spec);
continue;
}
const duration = item.metrics.avgDuration < 1000 ? DEFAULT_DURATION : Math.round(item.metrics.avgDuration);
output.specs[item.spec] = {
avgDuration: duration,
testCount: item.metrics.suiteSize,
flakyRate: Math.round(item.metrics.flakeRate * 10000) / 10000,
};
}
// Add new specs not yet in Currents
const newSpecs = [];
if (validSpecs) {
for (const spec of validSpecs) {
if (!output.specs[spec]) {
output.specs[spec] = { avgDuration: DEFAULT_DURATION, testCount: 0, flakyRate: 0 };
newSpecs.push(spec);
}
}
}
fs.mkdirSync(path.dirname(OUTPUT_PATH), { recursive: true });
fs.writeFileSync(OUTPUT_PATH, JSON.stringify(output, null, 2) + '\n');
console.log(`Wrote ${Object.keys(output.specs).length} specs`);
if (staleSpecs.length) {
console.log(`\nStale specs (in Currents but not Playwright):`);
staleSpecs.forEach((s) => console.log(` - ${s}`));
}
if (newSpecs.length) {
console.log(`\nNew specs (in Playwright but not Currents, using ${DEFAULT_DURATION / 1000}s default):`);
newSpecs.forEach((s) => console.log(` - ${s}`));
}
}
main().catch((e) => {
console.error(e.message);
process.exit(1);
});