Skip to content

chore: move update pr script to actual script with tests #1684

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
71 changes: 71 additions & 0 deletions .github/scripts/update-pr-description.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const { compareTests } = require('./utils/test');
const { generateTestChangesSummary } = require('./utils/format');
const { generateBundleSizeSection, getBundleInfo } = require('./utils/bundle');
const { readTestResults, getTestStatus } = require('./utils/results');

/**
* Main function to update PR description with test results and bundle size information
* @param {Object} github - GitHub API object
* @param {Object} context - GitHub Actions context
*/
async function updatePRDescription(github, context) {
// Read test results
const currentResults = readTestResults('playwright-artifacts/test-results.json');
const mainResults = readTestResults('gh-pages/main/test-results.json');

// Compare tests
const testComparison = compareTests(currentResults.tests, mainResults.tests);

// Get test status and report URL
const { status, statusColor } = getTestStatus(currentResults);
const reportUrl = `https://${context.repo.owner}.github.io/${context.repo.repo}/${context.issue.number}/`;

// Get bundle size information
const bundleInfo = getBundleInfo();

// Generate the CI section content
const ciSection = `## CI Results

### Test Status: <span style="color: ${statusColor};">${status}</span>
πŸ“Š [Full Report](${reportUrl})

| Total | Passed | Failed | Flaky | Skipped |
|:-----:|:------:|:------:|:-----:|:-------:|
| ${currentResults.total} | ${currentResults.passed} | ${currentResults.failed} | ${currentResults.flaky} | ${currentResults.skipped} |

${generateTestChangesSummary(testComparison)}

${generateBundleSizeSection(bundleInfo)}

<details>
<summary>ℹ️ CI Information</summary>

- Test recordings for failed tests are available in the full report.
- Bundle size is measured for the entire 'dist' directory.
- πŸ“Š indicates links to detailed reports.
- πŸ”Ί indicates increase, πŸ”½ decrease, and βœ… no change in bundle size.
</details>`;

// Update PR description
const { data: pullRequest } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});

const currentBody = pullRequest.body || '';
const ciSectionRegex = /## CI Results[\s\S]*?(?=\n## (?!CI Results)|$)/;

const newBody = ciSectionRegex.test(currentBody)
? currentBody.replace(ciSectionRegex, ciSection)
: currentBody + '\n\n' + ciSection;

await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
body: newBody,
});
}

module.exports = updatePRDescription;
40 changes: 40 additions & 0 deletions .github/scripts/utils/bundle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const { formatSize } = require('./format');

/**
* Generates the bundle size status section
* @param {Object} bundleInfo - Bundle size information
* @returns {string} Formatted bundle size section
*/
function generateBundleSizeSection({ currentSize, mainSize, diff, percent }) {
const bundleStatus = percent === 'N/A' ? '⚠️' :
parseFloat(percent) > 0 ? 'πŸ”Ί' :
parseFloat(percent) < 0 ? 'πŸ”½' : 'βœ…';

const sizeChangeMessage = percent === 'N/A' ? '⚠️ Unable to calculate change.' :
parseFloat(percent) > 0 ? '⚠️ Bundle size increased. Please review.' :
parseFloat(percent) < 0 ? 'βœ… Bundle size decreased.' : 'βœ… Bundle size unchanged.';

return `### Bundle Size: ${bundleStatus}
Current: ${formatSize(currentSize)} | Main: ${formatSize(mainSize)}
Diff: ${diff > 0 ? '+' : ''}${formatSize(Math.abs(diff))} (${percent === 'N/A' ? 'N/A' : `${percent}%`})

${sizeChangeMessage}`;
}

/**
* Gets bundle size information from environment variables
* @returns {Object} Bundle size information
*/
function getBundleInfo() {
return {
currentSize: parseInt(process.env.CURRENT_SIZE || '0'),
mainSize: parseInt(process.env.MAIN_SIZE || '0'),
diff: parseInt(process.env.SIZE_DIFF || '0'),
percent: process.env.SIZE_PERCENT || 'N/A'
};
}

module.exports = {
generateBundleSizeSection,
getBundleInfo
};
50 changes: 50 additions & 0 deletions .github/scripts/utils/format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Formats a size in bytes to a human-readable string (KB or MB)
* @param {number} sizeInBytes - Size in bytes to format
* @returns {string} Formatted size string with units
*/
function formatSize(sizeInBytes) {
const MB_THRESHOLD = 1024;
if (sizeInBytes >= MB_THRESHOLD) {
return `${(sizeInBytes / (1024 * 1024)).toFixed(2)} MB`;
}
return `${(sizeInBytes / 1024).toFixed(2)} KB`;
}

/**
* Generates a summary of test changes
* @param {Object} comparison - Test comparison results
* @returns {string} Formatted test changes summary
*/
function generateTestChangesSummary(comparison) {
if (!comparison.new.length && !comparison.deleted.length && !comparison.skipped.length) {
return '😟 No changes in tests. πŸ˜•';
}

const summaryParts = [];
const { new: newTests, skipped, deleted } = comparison;

if (newTests.length) {
summaryParts.push(`#### ✨ New Tests (${newTests.length})\n${newTests.map((test, i) => `${i + 1}. ${test}`).join('\n')}\n`);
}

if (skipped.length) {
summaryParts.push(`#### ⏭️ Skipped Tests (${skipped.length})\n${skipped.map((test, i) => `${i + 1}. ${test}`).join('\n')}\n`);
}

if (deleted.length) {
summaryParts.push(`#### πŸ—‘οΈ Deleted Tests (${deleted.length})\n${deleted.map((test, i) => `${i + 1}. ${test}`).join('\n')}`);
}

return `
<details>
<summary>Test Changes Summary ${newTests.length ? `✨${newTests.length} ` : ''}${skipped.length ? `⏭️${skipped.length} ` : ''}${deleted.length ? `πŸ—‘οΈ${deleted.length}` : ''}</summary>

${summaryParts.join('\n')}
</details>`;
}

module.exports = {
formatSize,
generateTestChangesSummary
};
48 changes: 48 additions & 0 deletions .github/scripts/utils/results.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const fs = require('fs');
const { extractTestsFromSuite } = require('./test');

/**
* Reads and processes test results from a JSON file
* @param {string} filePath - Path to the test results JSON file
* @returns {Object} Processed test results
*/
function readTestResults(filePath) {
if (!fs.existsSync(filePath)) {
console.log(`Test results file not found: ${filePath}`);
return { total: 0, passed: 0, failed: 0, flaky: 0, skipped: 0, tests: [] };
}

const data = JSON.parse(fs.readFileSync(filePath));
const allTests = data.suites.flatMap(suite => extractTestsFromSuite(suite));

return {
total: data.stats.expected + data.stats.unexpected + data.stats.flaky + data.stats.skipped,
passed: data.stats.expected,
failed: data.stats.unexpected,
flaky: data.stats.flaky,
skipped: data.stats.skipped,
tests: allTests
};
}

/**
* Gets the test status information
* @param {Object} results - Test results object
* @returns {Object} Status information including color and label
*/
function getTestStatus(results) {
const status = results.failed > 0 ? '❌ FAILED' :
results.flaky > 0 ? '⚠️ FLAKY' :
'βœ… PASSED';

const statusColor = results.failed > 0 ? 'red' :
results.flaky > 0 ? 'orange' :
'green';

return { status, statusColor };
}

module.exports = {
readTestResults,
getTestStatus
};
86 changes: 86 additions & 0 deletions .github/scripts/utils/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Checks if a test spec is marked as skipped
* @param {Object} spec - Test specification object
* @returns {boolean} True if the test is skipped
*/
function isTestSkipped(spec) {
return spec.tests?.[0] && (
spec.tests[0].annotations?.some(a => a.type === 'skip') ||
spec.tests[0].status === 'skipped'
);
}

/**
* Extracts test information from a test suite recursively
* @param {Object} suite - Test suite object
* @param {string} parentTitle - Parent suite title for nested suites
* @returns {Array} Array of test objects with metadata
*/
function extractTestsFromSuite(suite, parentTitle = '') {
const tests = [];
const fullSuiteTitle = parentTitle ? `${parentTitle} > ${suite.title}` : suite.title;

// Process individual test specs
if (suite.specs) {
const suiteTests = suite.specs.map(spec => {
const isSkipped = isTestSkipped(spec);
return {
title: spec.title,
fullTitle: `${fullSuiteTitle} > ${spec.title}`,
status: isSkipped ? 'skipped' : (spec.ok ? 'passed' : 'failed'),
file: suite.file,
skipped: isSkipped
};
});
tests.push(...suiteTests);
}

// Recursively process nested suites
if (suite.suites) {
suite.suites.forEach(nestedSuite => {
const nestedTests = extractTestsFromSuite(nestedSuite, fullSuiteTitle);
tests.push(...nestedTests);
});
}

return tests;
}

/**
* Compares current and main branch test results
* @param {Array} currentTests - Tests from current branch
* @param {Array} mainTests - Tests from main branch
* @returns {Object} Test comparison results
*/
function compareTests(currentTests, mainTests) {
const comparison = { new: [], skipped: [], deleted: [] };

const currentTestMap = new Map(currentTests.map(t => [t.fullTitle, t]));
const mainTestMap = new Map(mainTests.map(t => [t.fullTitle, t]));

// Find new and skipped tests
for (const [fullTitle, test] of currentTestMap) {
if (!mainTestMap.has(fullTitle)) {
comparison.new.push(`${test.title} (${test.file})`);
}
if (test.skipped) {
comparison.skipped.push(`${test.title} (${test.file})`);
}
}

// Find deleted tests
for (const [fullTitle, test] of mainTestMap) {
if (!currentTestMap.has(fullTitle)) {
comparison.deleted.push(`${test.title} (${test.file})`);
}
}

comparison.skipped = Array.from(new Set(comparison.skipped));
return comparison;
}

module.exports = {
isTestSkipped,
extractTestsFromSuite,
compareTests
};
Loading
Loading