diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/failed_tests_reporter_cli.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/failed_tests_reporter_cli.ts index 6b208f8f54d1a..51aaf471e2e99 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/failed_tests_reporter_cli.ts +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/failed_tests_reporter_cli.ts @@ -98,9 +98,6 @@ run( return; } - // Scout test failures reporting - await generateScoutTestFailureArtifacts({ log, bkMeta }); - if (reportPaths.length) { log.info('found', reportPaths.length, 'reports', reportPaths); @@ -137,6 +134,9 @@ run( // Process Scout reports await processScoutReports(scoutReports, processParams); + + // Generate Scout test failure artifacts after reports are updated (GH issue info, html reports, etc.) + await generateScoutTestFailureArtifacts({ log, bkMeta }); } } finally { await CiStatsReporter.fromEnv(log).metrics([ diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/process_scout_reports.test.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/process_scout_reports.test.ts new file mode 100644 index 0000000000000..73d3c2544c4f1 --- /dev/null +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/process_scout_reports.test.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import fs from 'fs'; +import os from 'os'; +import Path from 'path'; + +import { ToolingLog } from '@kbn/tooling-log'; + +import type { ScoutTestFailureExtended } from './get_scout_failures'; +import { updateScoutHtmlReport } from './process_scout_reports'; + +const createFailure = ( + overrides: Partial = {} +): ScoutTestFailureExtended => { + return { + id: 'failure-id', + target: 'serverless-oblt', + location: 'x-pack/test.ts', + duration: 1234, + owners: 'team:test', + classname: 'suite name', + name: 'test name', + time: '1.23', + failure: 'error message', + likelyIrrelevant: false, + ...overrides, + }; +}; + +describe('updateScoutHtmlReport', () => { + it('updates the tracked branches line when a GitHub issue exists', () => { + const tempDir = fs.mkdtempSync(Path.join(os.tmpdir(), 'scout-html-')); + const htmlPath = Path.join(tempDir, 'failure-id.html'); + const htmlTemplate = ` + + +
+ No failures found in tracked branches +
+ + + `; + fs.writeFileSync(htmlPath, htmlTemplate.trim(), 'utf-8'); + + const failure = createFailure({ + githubIssue: 'https://github.com/elastic/kibana/issues/123', + failureCount: 6, + }); + + updateScoutHtmlReport({ + log: new ToolingLog(), + reportDir: tempDir, + failure, + reportUpdate: true, + }); + + const updatedContent = fs.readFileSync(htmlPath, 'utf-8'); + expect(updatedContent).toContain('Failures in tracked branches'); + expect(updatedContent).toContain('id="failure-count">6<'); + expect(updatedContent).toContain('https://github.com/elastic/kibana/issues/123'); + }); +}); diff --git a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/process_scout_reports.ts b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/process_scout_reports.ts index 75cc67e5bfbea..d4b4730dbcae5 100644 --- a/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/process_scout_reports.ts +++ b/packages/kbn-failed-test-reporter-cli/failed_tests_reporter/process_scout_reports.ts @@ -7,11 +7,58 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import fs from 'fs'; +import { escape } from 'he'; +import Path from 'path'; import { getScoutFailures, type ScoutTestFailureExtended } from './get_scout_failures'; import type { ProcessReportsParams } from './process_reports_types'; import { createFailureIssue, updateFailureIssue } from './report_failure'; import { reportFailuresToEs } from './report_failures_to_es'; -import { reportFailuresToFile } from './report_failures_to_file'; + +export const updateScoutHtmlReport = ({ + log, + reportDir, + failure, + reportUpdate, +}: { + log: ProcessReportsParams['log']; + reportDir: string; + failure: ScoutTestFailureExtended; + reportUpdate: boolean; +}) => { + const htmlReportPath = Path.join(reportDir, `${failure.id}.html`); + if (!fs.existsSync(htmlReportPath)) { + log.warning(`Scout HTML report not found: ${htmlReportPath}`); + return; + } + + const fileContent = fs.readFileSync(htmlReportPath, 'utf-8'); + const failureCount = failure.failureCount ?? 0; + const githubIssue = failure.githubIssue ? escape(failure.githubIssue) : undefined; + + let updatedContent = fileContent; + if (githubIssue) { + const badgeHtml = `${failureCount}`; + const issueLinkHtml = `${githubIssue}`; + const trackedBranchesLine = `Failures in tracked branches: ${badgeHtml} ${issueLinkHtml}`; + + updatedContent = updatedContent.replace( + /]*id="tracked-branches-status"[^>]*>[\s\S]*?<\/div>/, + `
${trackedBranchesLine}
` + ); + } + + if (updatedContent === fileContent) { + return; + } + + if (!reportUpdate) { + log.info(`Report update disabled, skipping HTML update for ${htmlReportPath}`); + return; + } + + fs.writeFileSync(htmlReportPath, updatedContent, 'utf-8'); +}; export async function processScoutReports( reportPaths: string[], @@ -27,7 +74,7 @@ export async function processScoutReports( prependTitle, updateGithub, indexInEs, - bkMeta, + reportUpdate, } = params; for (const reportPath of reportPaths) { @@ -47,6 +94,8 @@ export async function processScoutReports( await reportFailuresToEs(log, failures); } + const reportDir = Path.dirname(reportPath); + for (const failure of failures) { if (failure.likelyIrrelevant) { log.info(`Scout failure is likely irrelevant: ${failure.classname} - ${failure.name}`); @@ -68,6 +117,7 @@ export async function processScoutReports( failure.githubIssue = url; failure.failureCount = updateGithub ? newCount : newCount - 1; log.info(`Updated existing Scout issue: ${url} (fail count: ${newCount})`); + updateScoutHtmlReport({ log, reportDir, failure, reportUpdate }); continue; } @@ -85,9 +135,7 @@ export async function processScoutReports( failure.githubIssue = newIssue.html_url; } failure.failureCount = updateGithub ? 1 : 0; + updateScoutHtmlReport({ log, reportDir, failure, reportUpdate }); } - - // Generate Scout failure artifacts (similar to JUnit report processing) - await reportFailuresToFile(log, failures, bkMeta, {}); } } diff --git a/src/platform/packages/private/kbn-scout-reporting/src/reporting/report/failed_test/html.ts b/src/platform/packages/private/kbn-scout-reporting/src/reporting/report/failed_test/html.ts index 9dd317d7d87b6..5387c692826c9 100644 --- a/src/platform/packages/private/kbn-scout-reporting/src/reporting/report/failed_test/html.ts +++ b/src/platform/packages/private/kbn-scout-reporting/src/reporting/report/failed_test/html.ts @@ -375,16 +375,8 @@ export const buildFailureHtml = (testFailure: TestFailure): string => {
${errorStackTrace}
-
-
- - Failures in tracked branches: - 0 - - -
+
+ No failures found in tracked branches