Skip to content

Commit b0936b9

Browse files
Copilotlitlfred
andcommitted
Fix QA workflow to post detailed test results table as PR comments with error context
Co-authored-by: litlfred <662242+litlfred@users.noreply.github.com>
1 parent 946426e commit b0936b9

2 files changed

Lines changed: 5456 additions & 695 deletions

File tree

‎.github/workflows/qa.yml‎

Lines changed: 214 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -29,104 +29,249 @@ jobs:
2929

3030
- name: Run linting
3131
id: lint
32-
run: npm run lint
32+
continue-on-error: true
33+
run: |
34+
npm run lint 2>&1 | tee lint-output.txt
3335
3436
- name: Build project
3537
id: build
36-
run: npm run build
38+
continue-on-error: true
39+
run: |
40+
npm run build 2>&1 | tee build-output.txt
3741
3842
- name: Run tests with coverage
39-
run: npm test -- --coverage --coverageReporters=text-lcov
43+
id: coverage
44+
continue-on-error: true
45+
run: |
46+
./node_modules/.bin/jest --testMatch="**/tests/**/*.test.ts" --coverage --coverageReporters=text-lcov 2>&1 | tee coverage-output.txt
4047
4148
- name: FML Compilation Tests
4249
id: fml-compilation
4350
continue-on-error: true
4451
run: |
4552
echo "=== FML Compilation Test Results ==="
46-
npm test -- --testPathPattern="fml-compiler|fhir-mapping-language" --verbose
53+
./node_modules/.bin/jest --testMatch="**/tests/**/*.test.ts" --testPathPattern="fml-compiler|fhir-mapping-language" --verbose 2>&1 | tee fml-compilation-output.txt
4754
4855
- name: FML Execution Tests
4956
id: fml-execution
5057
continue-on-error: true
5158
run: |
5259
echo "=== FML Execution Test Results ==="
53-
npm test -- --testPathPattern="structure-map-executor|fhirpath-integration" --verbose
60+
./node_modules/.bin/jest --testMatch="**/tests/**/*.test.ts" --testPathPattern="structure-map-executor|fhirpath-integration" --verbose 2>&1 | tee fml-execution-output.txt
5461
5562
- name: FHIR API Tests
5663
id: fhir-api
5764
continue-on-error: true
5865
run: |
5966
echo "=== FHIR API Test Results ==="
60-
npm test -- --testPathPattern="api|enhanced-api" --verbose
67+
./node_modules/.bin/jest --testMatch="**/tests/**/*.test.ts" --testPathPattern="api|enhanced-api" --verbose 2>&1 | tee fhir-api-output.txt
6168
6269
- name: Validation & Core Tests
6370
id: validation-core
6471
continue-on-error: true
6572
run: |
6673
echo "=== Validation & Core Test Results ==="
67-
npm test -- --testPathPattern="validation-service|fml-runner|structure-map-retriever|enhanced-tokenizer" --verbose
74+
./node_modules/.bin/jest --testMatch="**/tests/**/*.test.ts" --testPathPattern="validation-service|fml-runner|structure-map-retriever|enhanced-tokenizer" --verbose 2>&1 | tee validation-core-output.txt
6875
69-
- name: Generate QA Summary Table
76+
- name: Generate QA Summary Table and Post PR Comment
7077
if: always()
71-
run: |
72-
echo "## QA Report Summary - Node.js ${{ matrix.node-version }}" >> $GITHUB_STEP_SUMMARY
73-
echo "" >> $GITHUB_STEP_SUMMARY
74-
echo "| Test Category | Status | Details |" >> $GITHUB_STEP_SUMMARY
75-
echo "|---------------|--------|---------|" >> $GITHUB_STEP_SUMMARY
76-
77-
# Build Status
78-
if [ "${{ job.status }}" == "success" ] || [ "${{ steps.build.outcome }}" == "success" ]; then
79-
echo "| Build | ✅ Passed | TypeScript compilation successful |" >> $GITHUB_STEP_SUMMARY
80-
else
81-
echo "| Build | ❌ Failed | TypeScript compilation failed |" >> $GITHUB_STEP_SUMMARY
82-
fi
83-
84-
# Linting Status
85-
if [ "${{ steps.lint.outcome }}" == "success" ]; then
86-
echo "| Linting | ✅ Passed | ESLint validation successful |" >> $GITHUB_STEP_SUMMARY
87-
else
88-
echo "| Linting | ❌ Failed | ESLint validation failed |" >> $GITHUB_STEP_SUMMARY
89-
fi
90-
91-
# FML Compilation Tests
92-
if [ "${{ steps.fml-compilation.outcome }}" == "success" ]; then
93-
echo "| FML Compilation | ✅ Passed | FML parsing and compilation tests |" >> $GITHUB_STEP_SUMMARY
94-
else
95-
echo "| FML Compilation | ❌ Failed | FML parsing and compilation tests |" >> $GITHUB_STEP_SUMMARY
96-
fi
97-
98-
# FML Execution Tests
99-
if [ "${{ steps.fml-execution.outcome }}" == "success" ]; then
100-
echo "| FML Execution | ✅ Passed | StructureMap execution and FHIRPath tests |" >> $GITHUB_STEP_SUMMARY
101-
else
102-
echo "| FML Execution | ❌ Failed | StructureMap execution and FHIRPath tests |" >> $GITHUB_STEP_SUMMARY
103-
fi
104-
105-
# FHIR API Tests
106-
if [ "${{ steps.fhir-api.outcome }}" == "success" ]; then
107-
echo "| FHIR API | ✅ Passed | REST API endpoints and CRUD operations |" >> $GITHUB_STEP_SUMMARY
108-
else
109-
echo "| FHIR API | ❌ Failed | REST API endpoints and CRUD operations |" >> $GITHUB_STEP_SUMMARY
110-
fi
111-
112-
# Validation & Core Tests
113-
if [ "${{ steps.validation-core.outcome }}" == "success" ]; then
114-
echo "| Validation & Core | ✅ Passed | Input validation and core library functions |" >> $GITHUB_STEP_SUMMARY
115-
else
116-
echo "| Validation & Core | ❌ Failed | Input validation and core library functions |" >> $GITHUB_STEP_SUMMARY
117-
fi
118-
119-
echo "" >> $GITHUB_STEP_SUMMARY
120-
121-
# Overall Summary
122-
TOTAL_TESTS=$(npm test 2>&1 | grep -o 'Tests:.*[0-9]* passed' | grep -o '[0-9]* passed' | head -1 || echo "unknown passed")
123-
TOTAL_SUITES=$(npm test 2>&1 | grep -o 'Test Suites:.*[0-9]* passed' | grep -o '[0-9]* passed' | head -1 || echo "unknown passed")
124-
125-
echo "### Summary" >> $GITHUB_STEP_SUMMARY
126-
echo "- **Node.js Version:** ${{ matrix.node-version }}" >> $GITHUB_STEP_SUMMARY
127-
echo "- **Total Tests:** $TOTAL_TESTS" >> $GITHUB_STEP_SUMMARY
128-
echo "- **Test Suites:** $TOTAL_SUITES" >> $GITHUB_STEP_SUMMARY
129-
echo "- **Overall Status:** $([ "${{ job.status }}" == "success" ] && echo "✅ All QA checks passed" || echo "❌ Some QA checks failed")" >> $GITHUB_STEP_SUMMARY
78+
uses: actions/github-script@v7
79+
with:
80+
script: |
81+
const fs = require('fs');
82+
83+
// Helper function to read file safely
84+
function readOutputFile(filename) {
85+
try {
86+
if (fs.existsSync(filename)) {
87+
return fs.readFileSync(filename, 'utf8');
88+
}
89+
return '';
90+
} catch (error) {
91+
return `Error reading ${filename}: ${error.message}`;
92+
}
93+
}
94+
95+
// Helper function to extract error context from output
96+
function extractErrorContext(output, maxLines = 10) {
97+
if (!output) return 'No output captured';
98+
99+
const lines = output.split('\n');
100+
const errorLines = [];
101+
let capturing = false;
102+
103+
for (let i = 0; i < lines.length && errorLines.length < maxLines; i++) {
104+
const line = lines[i];
105+
106+
// Look for error indicators
107+
if (line.includes('FAIL') || line.includes('Error:') || line.includes('Failed:') ||
108+
line.includes('● ') || line.includes('✕') || line.includes('ERRORS:')) {
109+
capturing = true;
110+
}
111+
112+
if (capturing) {
113+
errorLines.push(line);
114+
// Stop capturing after finding summary or next test
115+
if (line.includes('Test Suites:') || line.includes('Tests:')) {
116+
break;
117+
}
118+
}
119+
}
120+
121+
return errorLines.length > 0 ? errorLines.slice(0, maxLines).join('\n') : 'No specific error details captured';
122+
}
123+
124+
// Get step outcomes
125+
const outcomes = {
126+
lint: '${{ steps.lint.outcome }}',
127+
build: '${{ steps.build.outcome }}',
128+
coverage: '${{ steps.coverage.outcome }}',
129+
fmlCompilation: '${{ steps.fml-compilation.outcome }}',
130+
fmlExecution: '${{ steps.fml-execution.outcome }}',
131+
fhirApi: '${{ steps.fhir-api.outcome }}',
132+
validationCore: '${{ steps.validation-core.outcome }}'
133+
};
134+
135+
// Read output files
136+
const outputs = {
137+
lint: readOutputFile('lint-output.txt'),
138+
build: readOutputFile('build-output.txt'),
139+
coverage: readOutputFile('coverage-output.txt'),
140+
fmlCompilation: readOutputFile('fml-compilation-output.txt'),
141+
fmlExecution: readOutputFile('fml-execution-output.txt'),
142+
fhirApi: readOutputFile('fhir-api-output.txt'),
143+
validationCore: readOutputFile('validation-core-output.txt')
144+
};
145+
146+
// Extract test summary from coverage output
147+
const coverageOutput = outputs.coverage;
148+
let totalTests = 'unknown';
149+
let totalSuites = 'unknown';
150+
151+
const testMatch = coverageOutput.match(/Tests:\s*(\d+\s+\w+)/);
152+
const suiteMatch = coverageOutput.match(/Test Suites:\s*(\d+\s+\w+)/);
153+
154+
if (testMatch) totalTests = testMatch[1];
155+
if (suiteMatch) totalSuites = suiteMatch[1];
156+
157+
// Build QA table
158+
let qaTable = `## QA Report Summary - Node.js ${{ matrix.node-version }}
159+
160+
| Test Category | Status | Details | Error Context |
161+
|---------------|--------|---------|---------------|`;
162+
163+
// Build Status
164+
if (outcomes.build === 'success') {
165+
qaTable += `\n| Build | ✅ Passed | TypeScript compilation successful | - |`;
166+
} else {
167+
const errorContext = extractErrorContext(outputs.build, 5);
168+
qaTable += `\n| Build | ❌ Failed | TypeScript compilation failed | \`\`\`\n${errorContext}\n\`\`\` |`;
169+
}
170+
171+
// Linting Status
172+
if (outcomes.lint === 'success') {
173+
qaTable += `\n| Linting | ✅ Passed | ESLint validation successful | - |`;
174+
} else {
175+
const errorContext = extractErrorContext(outputs.lint, 5);
176+
qaTable += `\n| Linting | ❌ Failed | ESLint validation failed | \`\`\`\n${errorContext}\n\`\`\` |`;
177+
}
178+
179+
// FML Compilation Tests
180+
if (outcomes.fmlCompilation === 'success') {
181+
qaTable += `\n| FML Compilation | ✅ Passed | FML parsing and compilation tests | - |`;
182+
} else {
183+
const errorContext = extractErrorContext(outputs.fmlCompilation, 8);
184+
qaTable += `\n| FML Compilation | ❌ Failed | FML parsing and compilation tests | \`\`\`\n${errorContext}\n\`\`\` |`;
185+
}
186+
187+
// FML Execution Tests
188+
if (outcomes.fmlExecution === 'success') {
189+
qaTable += `\n| FML Execution | ✅ Passed | StructureMap execution and FHIRPath tests | - |`;
190+
} else {
191+
const errorContext = extractErrorContext(outputs.fmlExecution, 8);
192+
qaTable += `\n| FML Execution | ❌ Failed | StructureMap execution and FHIRPath tests | \`\`\`\n${errorContext}\n\`\`\` |`;
193+
}
194+
195+
// FHIR API Tests
196+
if (outcomes.fhirApi === 'success') {
197+
qaTable += `\n| FHIR API | ✅ Passed | REST API endpoints and CRUD operations | - |`;
198+
} else {
199+
const errorContext = extractErrorContext(outputs.fhirApi, 8);
200+
qaTable += `\n| FHIR API | ❌ Failed | REST API endpoints and CRUD operations | \`\`\`\n${errorContext}\n\`\`\` |`;
201+
}
202+
203+
// Validation & Core Tests
204+
if (outcomes.validationCore === 'success') {
205+
qaTable += `\n| Validation & Core | ✅ Passed | Input validation and core library functions | - |`;
206+
} else {
207+
const errorContext = extractErrorContext(outputs.validationCore, 8);
208+
qaTable += `\n| Validation & Core | ❌ Failed | Input validation and core library functions | \`\`\`\n${errorContext}\n\`\`\` |`;
209+
}
210+
211+
// Overall Summary
212+
const overallStatus = Object.values(outcomes).every(outcome => outcome === 'success') ? '✅ All QA checks passed' : '❌ Some QA checks failed';
213+
214+
qaTable += `
215+
216+
### Summary
217+
- **Node.js Version:** ${{ matrix.node-version }}
218+
- **Total Tests:** ${totalTests}
219+
- **Test Suites:** ${totalSuites}
220+
- **Overall Status:** ${overallStatus}
221+
222+
<details>
223+
<summary>🔍 View Full Test Output</summary>
224+
225+
**Coverage Output:**
226+
\`\`\`
227+
${outputs.coverage.split('\n').slice(-20).join('\n')}
228+
\`\`\`
229+
230+
</details>`;
231+
232+
// Post comment to PR if this is a pull request
233+
if (context.eventName === 'pull_request') {
234+
try {
235+
// Check if a comment already exists from this workflow
236+
const { data: comments } = await github.rest.issues.listComments({
237+
owner: context.repo.owner,
238+
repo: context.repo.repo,
239+
issue_number: context.issue.number,
240+
});
241+
242+
const existingComment = comments.find(comment =>
243+
comment.user.login === 'github-actions[bot]' &&
244+
comment.body.includes(`QA Report Summary - Node.js ${{ matrix.node-version }}`)
245+
);
246+
247+
if (existingComment) {
248+
// Update existing comment
249+
await github.rest.issues.updateComment({
250+
owner: context.repo.owner,
251+
repo: context.repo.repo,
252+
comment_id: existingComment.id,
253+
body: qaTable
254+
});
255+
console.log('Updated existing QA report comment');
256+
} else {
257+
// Create new comment
258+
await github.rest.issues.createComment({
259+
owner: context.repo.owner,
260+
repo: context.repo.repo,
261+
issue_number: context.issue.number,
262+
body: qaTable
263+
});
264+
console.log('Created new QA report comment');
265+
}
266+
} catch (error) {
267+
console.error('Error posting PR comment:', error);
268+
// Fall back to step summary
269+
core.summary.addRaw(qaTable).write();
270+
}
271+
} else {
272+
// For push events, just write to step summary
273+
core.summary.addRaw(qaTable).write();
274+
}
130275

131276
- name: Upload test artifacts
132277
uses: actions/upload-artifact@v4
@@ -135,4 +280,5 @@ jobs:
135280
name: test-results-node-${{ matrix.node-version }}
136281
path: |
137282
coverage/
138-
dist/
283+
dist/
284+
*-output.txt

0 commit comments

Comments
 (0)