From 5b64dd2130078dc65f2469994aad5064405126a5 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 13 Apr 2026 19:28:53 -0300 Subject: [PATCH 1/2] chore: improve JIRA Playwright reporter observability Log each decision point (search count, reuse vs create) and validate HTTP responses so API failures surface in CI logs with status and body preview. Clarify the initial log line so it is not mistaken for a successful JIRA write. Fix the GitHub permalink in the new-issue comment template (stray comma). Made-with: Cursor --- apps/meteor/reporters/jira.ts | 68 +++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/apps/meteor/reporters/jira.ts b/apps/meteor/reporters/jira.ts index e2b86ab52161d..c6666b4bcbeb4 100644 --- a/apps/meteor/reporters/jira.ts +++ b/apps/meteor/reporters/jira.ts @@ -1,6 +1,8 @@ import type { Reporter, TestCase, TestResult } from '@playwright/test/reporter'; import fetch from 'node-fetch'; +const LOG = '[JIRA reporter]'; + class JIRAReporter implements Reporter { private url: string; @@ -42,11 +44,20 @@ class JIRAReporter implements Reporter { this.pr = options.pr; } + private static async ensureJiraOk(response: Response, context: string): Promise { + if (response.ok) { + return; + } + const text = await response.text(); + const preview = text.length > 800 ? `${text.slice(0, 800)}…` : text; + throw new Error(`${LOG} ${context}: HTTP ${response.status} ${response.statusText}. Body: ${preview}`); + } + async onTestEnd(test: TestCase, result: TestResult) { try { await this._onTestEnd(test, result); } catch (error) { - console.error('Error sending test result to JIRA', error); + console.error(`${LOG} Error sending test result to JIRA`, error); } } @@ -77,7 +88,7 @@ class JIRAReporter implements Reporter { headSha: this.headSha, }; - console.log(`Sending test result to JIRA: ${JSON.stringify(payload)}`); + console.log(`${LOG} preparing notification for flaky/unexpected failure: ${JSON.stringify(payload)}`); // first search and check if there is an existing issue // replace all ()[]- with nothing @@ -94,30 +105,27 @@ class JIRAReporter implements Reporter { }, ); - if (!search.ok) { - throw new Error( - `JIRA: Failed to search for existing issue: ${search.statusText}.` + - `${this.url}/rest/api/3/search/jql?${new URLSearchParams({ - jql: `project = FLAKY AND summary ~ '${payload.name}'`, - })}`, - ); - } + await JIRAReporter.ensureJiraOk(search, 'search for existing issue'); - const { issues } = await search.json(); + const { issues } = (await search.json()) as { + issues: { key: string; fields: { summary: string } }[]; + }; - const existing = issues.find( - (issue: { - fields: { - summary: string; - }; - }) => issue.fields.summary === payload.name, + console.log( + `${LOG} JQL search returned ${issues.length} candidate issue(s) (exact summary match is applied next).`, ); + const existing = issues.find((issue) => issue.fields.summary === payload.name); + if (existing) { + console.log( + `${LOG} exact summary match on ${existing.key}; no new issue will be created (comment / label only).`, + ); + const { location } = test; if (this.pr === 0) { - await fetch(`${this.url}/rest/api/3/issue/${existing.key}`, { + const labelRes = await fetch(`${this.url}/rest/api/3/issue/${existing.key}`, { method: 'PUT', body: JSON.stringify({ update: { @@ -133,9 +141,11 @@ class JIRAReporter implements Reporter { 'Authorization': `Basic ${this.apiKey}`, }, }); + await JIRAReporter.ensureJiraOk(labelRes, `add label flaky_Develop on ${existing.key}`); + console.log(`${LOG} label update OK for ${existing.key}`); } - await fetch(`${this.url}/rest/api/3/issue/${existing.key}/comment`, { + const commentRes = await fetch(`${this.url}/rest/api/3/issue/${existing.key}/comment`, { method: 'POST', body: JSON.stringify({ body: `Test run ${payload.run} failed @@ -153,9 +163,13 @@ ${this.run_url} 'Authorization': `Basic ${this.apiKey}`, }, }); + await JIRAReporter.ensureJiraOk(commentRes, `comment on ${existing.key}`); + console.log(`${LOG} comment posted on ${existing.key} for run ${payload.run}.`); return; } + console.log(`${LOG} no issue with identical summary; creating new FLAKY issue.`); + const data: { fields: { summary: string; @@ -190,11 +204,19 @@ ${this.run_url} }, }); - const issue = (await responseIssue.json()).key; + await JIRAReporter.ensureJiraOk(responseIssue, 'create issue'); + + const created = (await responseIssue.json()) as { key?: string }; + const issue = created.key; + if (!issue) { + throw new Error(`${LOG} create issue response had no key: ${JSON.stringify(created)}`); + } + + console.log(`${LOG} created issue ${issue}.`); const { location } = test; - await fetch(`${this.url}/rest/api/3/issue/${issue}/comment`, { + const commentRes = await fetch(`${this.url}/rest/api/3/issue/${issue}/comment`, { method: 'POST', body: JSON.stringify({ body: `Test run ${payload.run} failed @@ -203,7 +225,7 @@ PR: ${this.pr} https://github.com/RocketChat/Rocket.Chat/blob/${payload.headSha}/${location.file.replace( '/home/runner/work/Rocket.Chat/Rocket.Chat', '', - )}#L${location.line}:${location.column}, + )}#L${location.line}:${location.column} ${this.run_url} `, }), @@ -212,6 +234,8 @@ ${this.run_url} 'Authorization': `Basic ${this.apiKey}`, }, }); + await JIRAReporter.ensureJiraOk(commentRes, `comment on ${issue}`); + console.log(`${LOG} comment posted on ${issue}; done for run ${payload.run}.`); } } From dc5313b0dafd9de9d9055628094127d1e2f22b3c Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 13 Apr 2026 20:48:23 -0300 Subject: [PATCH 2/2] fix: satisfy ESLint for JIRA reporter (regex, JSON parse, prettier) - Avoid no-useless-escape in JQL summary sanitizer - Replace response.json() with JSON.parse(text) for no-unsafe-call - Collapse console.log strings for prettier - Type ensureJiraOk param as Awaited> - Use ASCII ellipsis in error body preview Made-with: Cursor --- apps/meteor/reporters/jira.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/apps/meteor/reporters/jira.ts b/apps/meteor/reporters/jira.ts index c6666b4bcbeb4..390116dbabfd7 100644 --- a/apps/meteor/reporters/jira.ts +++ b/apps/meteor/reporters/jira.ts @@ -44,12 +44,12 @@ class JIRAReporter implements Reporter { this.pr = options.pr; } - private static async ensureJiraOk(response: Response, context: string): Promise { + private static async ensureJiraOk(response: Awaited>, context: string): Promise { if (response.ok) { return; } const text = await response.text(); - const preview = text.length > 800 ? `${text.slice(0, 800)}…` : text; + const preview = text.length > 800 ? `${text.slice(0, 800)}...` : text; throw new Error(`${LOG} ${context}: HTTP ${response.status} ${response.statusText}. Body: ${preview}`); } @@ -94,7 +94,7 @@ class JIRAReporter implements Reporter { // replace all ()[]- with nothing const search = await fetch( `${this.url}/rest/api/3/search/jql?${new URLSearchParams({ - jql: `project = FLAKY AND summary ~ '${payload.name.replace(/[\(\)\[\]-]/g, '')}'`, + jql: `project = FLAKY AND summary ~ '${payload.name.replace(/[()[\]-]/g, '')}'`, })}`, { method: 'GET', @@ -107,20 +107,16 @@ class JIRAReporter implements Reporter { await JIRAReporter.ensureJiraOk(search, 'search for existing issue'); - const { issues } = (await search.json()) as { + const { issues } = JSON.parse(await search.text()) as { issues: { key: string; fields: { summary: string } }[]; }; - console.log( - `${LOG} JQL search returned ${issues.length} candidate issue(s) (exact summary match is applied next).`, - ); + console.log(`${LOG} JQL search returned ${issues.length} candidate issue(s) (exact summary match is applied next).`); const existing = issues.find((issue) => issue.fields.summary === payload.name); if (existing) { - console.log( - `${LOG} exact summary match on ${existing.key}; no new issue will be created (comment / label only).`, - ); + console.log(`${LOG} exact summary match on ${existing.key}; no new issue will be created (comment / label only).`); const { location } = test; @@ -206,7 +202,7 @@ ${this.run_url} await JIRAReporter.ensureJiraOk(responseIssue, 'create issue'); - const created = (await responseIssue.json()) as { key?: string }; + const created = JSON.parse(await responseIssue.text()) as { key?: string }; const issue = created.key; if (!issue) { throw new Error(`${LOG} create issue response had no key: ${JSON.stringify(created)}`);