@@ -269,8 +269,63 @@ jobs:
269269 });
270270 }
271271
272+ function cleanLine(line) {
273+ return line
274+ .replace(/\u001b\[[0-9;]*m/g, '')
275+ .trim()
276+ .slice(0, 300);
277+ }
278+
279+ function extractFailureSnippet(logPath) {
280+ if (!fs.existsSync(logPath)) return '';
281+
282+ const lines = fs.readFileSync(logPath, 'utf8').split(/\r?\n/);
283+ const importantPatterns = [
284+ /\.swift:\d+:\d+:\s+error:/,
285+ /Testing failed:/,
286+ /\*\* TEST FAILED \*\*/,
287+ /The following build commands failed:/,
288+ /Issue recorded:/,
289+ /Expectation failed:/,
290+ /✘/,
291+ ];
292+ const indexes = [];
293+
294+ lines.forEach((line, index) => {
295+ if (importantPatterns.some((pattern) => pattern.test(line))) {
296+ indexes.push(index);
297+ }
298+ });
299+
300+ const selectedLines = [];
301+ const seenLines = new Set();
302+
303+ for (const index of indexes.slice(0, 5)) {
304+ const start = Math.max(0, index - 2);
305+ const end = Math.min(lines.length, index + 9);
306+ for (const line of lines.slice(start, end)) {
307+ const cleanedLine = cleanLine(line);
308+ if (!cleanedLine || seenLines.has(cleanedLine)) continue;
309+ seenLines.add(cleanedLine);
310+ selectedLines.push(cleanedLine);
311+ }
312+ }
313+
314+ if (selectedLines.length <= 0) {
315+ for (const line of lines.slice(-25)) {
316+ const cleanedLine = cleanLine(line);
317+ if (!cleanedLine || seenLines.has(cleanedLine)) continue;
318+ seenLines.add(cleanedLine);
319+ selectedLines.push(cleanedLine);
320+ }
321+ }
322+
323+ return selectedLines.slice(0, 30).join('\n');
324+ }
325+
272326 for (const summaryPath of walk(root).filter((filePath) => path.basename(filePath) === 'summary.txt')) {
273327 const artifactName = summaryPath.split(path.sep)[1] || 'unknown';
328+ const artifactDirectory = path.dirname(summaryPath);
274329 const text = fs.readFileSync(summaryPath, 'utf8');
275330 const records = text
276331 .trim()
@@ -287,25 +342,31 @@ jobs:
287342
288343 for (const record of records) {
289344 if (record.status && record.status !== '0') {
345+ const scheme = record.scheme || 'unknown';
346+ const logPath = path.join(artifactDirectory, `${scheme}.log`);
290347 summaries.push({
291348 artifactName,
292- scheme: record.scheme || 'unknown' ,
349+ scheme,
293350 status: record.status,
351+ snippet: extractFailureSnippet(logPath),
294352 });
295353 }
296354 }
297355 }
298356
299- let body = '❌ iOS tests failed.\n\n';
357+ let body = '❌ iOS CI tests failed.\n\n';
300358
301359 if (summaries.length <= 0) {
302360 body += 'No failed scheme summary was found. Check the uploaded test log artifacts.';
303361 } else {
304362 body += 'Failed schemes:\n\n';
305- body += summaries
306- .map((summary) => `- ${summary.scheme} (${summary.artifactName}, exit ${summary.status})`)
307- .join('\n');
308- body += '\n\nCheck the uploaded test log artifacts for full diagnostics.';
363+ for (const summary of summaries) {
364+ body += `- ${summary.scheme} (${summary.artifactName})\n`;
365+ if (summary.snippet) {
366+ body += '\n```text\n' + summary.snippet + '\n```\n\n';
367+ }
368+ }
369+ body += '\nCheck the uploaded test log artifacts for full diagnostics.';
309370 }
310371
311372 if (!context.payload.pull_request) {
0 commit comments