@browserstack/mcp-server 1.2.15-beta.2 → 1.2.15-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/percy-api/build-items.d.ts +31 -0
- package/dist/lib/percy-api/build-items.js +61 -0
- package/dist/lib/percy-api/percy-auth.d.ts +1 -1
- package/dist/lib/percy-api/percy-auth.js +6 -1
- package/dist/tools/percy-mcp/v2/get-build-detail.js +12 -13
- package/dist/tools/percy-mcp/v2/get-comparison.js +3 -2
- package/dist/tools/percy-mcp/v2/get-snapshot.js +4 -7
- package/dist/tools/percy-mcp/v2/search-build-items.js +29 -15
- package/dist/tools/percy-mcp/workflows/auto-triage.js +3 -2
- package/dist/tools/percy-mcp/workflows/diff-explain.js +3 -2
- package/dist/tools/percy-mcp/workflows/pr-visual-report.js +4 -3
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the /build-items endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Pagination: the API returns everything when page[limit] is omitted, but may
|
|
5
|
+
* still paginate (meta.pagination.has_more + next_cursor) on very large
|
|
6
|
+
* builds or if server-side defaults change. fetchAllBuildItems handles both:
|
|
7
|
+
* it starts with an unbounded request and follows next_cursor if the response
|
|
8
|
+
* is paginated anyway.
|
|
9
|
+
*/
|
|
10
|
+
import { BrowserStackConfig } from "../types.js";
|
|
11
|
+
export interface BuildItemsResult {
|
|
12
|
+
items: any[];
|
|
13
|
+
/** True if pagination could not be exhausted (page cap or cursor stall). */
|
|
14
|
+
truncated: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Fetch ALL build items for the given filters, following
|
|
18
|
+
* meta.pagination.next_cursor until has_more is false.
|
|
19
|
+
*/
|
|
20
|
+
export declare function fetchAllBuildItems(config: BrowserStackConfig, baseParams: Record<string, string | string[]>): Promise<BuildItemsResult>;
|
|
21
|
+
/**
|
|
22
|
+
* Filter params that make filter[category]=changed actually return results.
|
|
23
|
+
* The API only maps the changed category to review states via
|
|
24
|
+
* filter[subcategories][]; without it the scope resolves to zero rows.
|
|
25
|
+
*/
|
|
26
|
+
export declare const CHANGED_CATEGORY_PARAMS: Record<string, string | string[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Format a 0..1 diff ratio as a percentage without hiding small diffs:
|
|
29
|
+
* 0 → "0%", tiny → "<0.01%", otherwise two decimals (e.g. "0.02%").
|
|
30
|
+
*/
|
|
31
|
+
export declare function formatDiffPercent(ratio: number | null | undefined): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the /build-items endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Pagination: the API returns everything when page[limit] is omitted, but may
|
|
5
|
+
* still paginate (meta.pagination.has_more + next_cursor) on very large
|
|
6
|
+
* builds or if server-side defaults change. fetchAllBuildItems handles both:
|
|
7
|
+
* it starts with an unbounded request and follows next_cursor if the response
|
|
8
|
+
* is paginated anyway.
|
|
9
|
+
*/
|
|
10
|
+
import { percyGet } from "./percy-auth.js";
|
|
11
|
+
const MAX_PAGES = 50;
|
|
12
|
+
/**
|
|
13
|
+
* Fetch ALL build items for the given filters, following
|
|
14
|
+
* meta.pagination.next_cursor until has_more is false.
|
|
15
|
+
*/
|
|
16
|
+
export async function fetchAllBuildItems(config, baseParams) {
|
|
17
|
+
const items = [];
|
|
18
|
+
let cursor;
|
|
19
|
+
let pages = 0;
|
|
20
|
+
for (;;) {
|
|
21
|
+
const params = { ...baseParams };
|
|
22
|
+
if (cursor)
|
|
23
|
+
params["page[cursor]"] = cursor;
|
|
24
|
+
const response = await percyGet("/build-items", config, params);
|
|
25
|
+
items.push(...(response?.data || []));
|
|
26
|
+
pages += 1;
|
|
27
|
+
const pagination = response?.meta?.pagination;
|
|
28
|
+
const nextCursor = pagination?.next_cursor;
|
|
29
|
+
if (!pagination?.has_more || !nextCursor) {
|
|
30
|
+
return { items, truncated: false };
|
|
31
|
+
}
|
|
32
|
+
// Guard against a stalled cursor or runaway loop.
|
|
33
|
+
if (nextCursor === cursor || pages >= MAX_PAGES) {
|
|
34
|
+
return { items, truncated: true };
|
|
35
|
+
}
|
|
36
|
+
cursor = String(nextCursor);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Filter params that make filter[category]=changed actually return results.
|
|
41
|
+
* The API only maps the changed category to review states via
|
|
42
|
+
* filter[subcategories][]; without it the scope resolves to zero rows.
|
|
43
|
+
*/
|
|
44
|
+
export const CHANGED_CATEGORY_PARAMS = {
|
|
45
|
+
"filter[category]": "changed",
|
|
46
|
+
"filter[subcategories][]": ["unreviewed", "changes_requested", "approved"],
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Format a 0..1 diff ratio as a percentage without hiding small diffs:
|
|
50
|
+
* 0 → "0%", tiny → "<0.01%", otherwise two decimals (e.g. "0.02%").
|
|
51
|
+
*/
|
|
52
|
+
export function formatDiffPercent(ratio) {
|
|
53
|
+
if (ratio == null)
|
|
54
|
+
return "—";
|
|
55
|
+
const pct = ratio * 100;
|
|
56
|
+
if (pct === 0)
|
|
57
|
+
return "0%";
|
|
58
|
+
if (pct < 0.01)
|
|
59
|
+
return "<0.01%";
|
|
60
|
+
return pct.toFixed(2) + "%";
|
|
61
|
+
}
|
|
@@ -22,7 +22,7 @@ export declare function getPercyTokenHeaders(token: string): Record<string, stri
|
|
|
22
22
|
/**
|
|
23
23
|
* Make a GET request to Percy API with Basic Auth.
|
|
24
24
|
*/
|
|
25
|
-
export declare function percyGet(path: string, config: BrowserStackConfig, params?: Record<string, string>): Promise<any>;
|
|
25
|
+
export declare function percyGet(path: string, config: BrowserStackConfig, params?: Record<string, string | string[]>): Promise<any>;
|
|
26
26
|
/**
|
|
27
27
|
* Make a POST request to Percy API with Basic Auth.
|
|
28
28
|
*/
|
|
@@ -42,7 +42,12 @@ export async function percyGet(path, config, params) {
|
|
|
42
42
|
const url = new URL(`${PERCY_API_BASE}${path}`);
|
|
43
43
|
if (params) {
|
|
44
44
|
for (const [key, value] of Object.entries(params)) {
|
|
45
|
-
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
value.forEach((v) => url.searchParams.append(key, v));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
url.searchParams.set(key, value);
|
|
50
|
+
}
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
const response = await fetch(url.toString(), { headers });
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* - snapshots: all snapshots with review states
|
|
12
12
|
*/
|
|
13
13
|
import { percyGet, percyPost } from "../../../lib/percy-api/percy-auth.js";
|
|
14
|
+
import { fetchAllBuildItems, formatDiffPercent, CHANGED_CATEGORY_PARAMS, } from "../../../lib/percy-api/build-items.js";
|
|
14
15
|
import { setActiveBuild } from "../../../lib/percy-api/percy-session.js";
|
|
15
16
|
export async function percyGetBuildDetail(args, config) {
|
|
16
17
|
const detail = args.detail || "overview";
|
|
@@ -253,12 +254,10 @@ async function getAiSummary(buildId, config) {
|
|
|
253
254
|
}
|
|
254
255
|
// ── Changes ─────────────────────────────────────────────────────────────────
|
|
255
256
|
async function getChanges(buildId, config) {
|
|
256
|
-
const
|
|
257
|
+
const { items, truncated } = await fetchAllBuildItems(config, {
|
|
257
258
|
"filter[build-id]": buildId,
|
|
258
|
-
|
|
259
|
-
"page[limit]": "30",
|
|
259
|
+
...CHANGED_CATEGORY_PARAMS,
|
|
260
260
|
});
|
|
261
|
-
const items = response?.data || [];
|
|
262
261
|
if (!items.length) {
|
|
263
262
|
return {
|
|
264
263
|
content: [
|
|
@@ -275,14 +274,15 @@ async function getChanges(buildId, config) {
|
|
|
275
274
|
const a = item.attributes || item;
|
|
276
275
|
const name = a["cover-snapshot-name"] || a.coverSnapshotName || "?";
|
|
277
276
|
const displayName = a["cover-snapshot-display-name"] || a.coverSnapshotDisplayName || "";
|
|
278
|
-
const diff = (a["max-diff-ratio"] ?? a.maxDiffRatio)
|
|
279
|
-
? ((a["max-diff-ratio"] ?? a.maxDiffRatio) * 100).toFixed(1) + "%"
|
|
280
|
-
: "—";
|
|
277
|
+
const diff = formatDiffPercent(a["max-diff-ratio"] ?? a.maxDiffRatio);
|
|
281
278
|
const bugs = a["max-bug-total-potential-bugs"] ?? a.maxBugTotalPotentialBugs ?? 0;
|
|
282
279
|
const review = a["review-state"] || a.reviewState || "?";
|
|
283
280
|
const count = a["item-count"] || a.itemCount || 1;
|
|
284
281
|
output += `| ${i + 1} | ${name} | ${displayName || "—"} | ${diff} | ${bugs} | ${review} | ${count} |\n`;
|
|
285
282
|
});
|
|
283
|
+
if (truncated) {
|
|
284
|
+
output += `\n⚠️ Result may be incomplete — pagination could not be fully exhausted.\n`;
|
|
285
|
+
}
|
|
286
286
|
output += `\nUse \`percy_get_snapshot\` with a snapshot ID from above for full details.\n`;
|
|
287
287
|
return { content: [{ type: "text", text: output }] };
|
|
288
288
|
}
|
|
@@ -523,11 +523,9 @@ async function getNetwork(args, config) {
|
|
|
523
523
|
}
|
|
524
524
|
// ── Snapshots ───────────────────────────────────────────────────────────────
|
|
525
525
|
async function getSnapshots(buildId, config) {
|
|
526
|
-
const
|
|
526
|
+
const { items, truncated } = await fetchAllBuildItems(config, {
|
|
527
527
|
"filter[build-id]": buildId,
|
|
528
|
-
"page[limit]": "30",
|
|
529
528
|
});
|
|
530
|
-
const items = response?.data || [];
|
|
531
529
|
if (!items.length) {
|
|
532
530
|
return {
|
|
533
531
|
content: [
|
|
@@ -550,9 +548,7 @@ async function getSnapshots(buildId, config) {
|
|
|
550
548
|
const a = item.attributes || item;
|
|
551
549
|
const name = a["cover-snapshot-name"] || a.coverSnapshotName || "?";
|
|
552
550
|
const display = a["cover-snapshot-display-name"] || a.coverSnapshotDisplayName || "—";
|
|
553
|
-
const diff = (a["max-diff-ratio"] ?? a.maxDiffRatio)
|
|
554
|
-
? ((a["max-diff-ratio"] ?? a.maxDiffRatio) * 100).toFixed(1) + "%"
|
|
555
|
-
: "—";
|
|
551
|
+
const diff = formatDiffPercent(a["max-diff-ratio"] ?? a.maxDiffRatio);
|
|
556
552
|
const bugs = a["max-bug-total-potential-bugs"] ?? a.maxBugTotalPotentialBugs ?? "—";
|
|
557
553
|
const review = a["review-state"] || a.reviewState || "?";
|
|
558
554
|
const count = a["item-count"] || a.itemCount || 1;
|
|
@@ -562,6 +558,9 @@ async function getSnapshots(buildId, config) {
|
|
|
562
558
|
const more = (a["snapshot-ids"] || a.snapshotIds || []).length > 3 ? "..." : "";
|
|
563
559
|
output += `| ${i + 1} | ${name} | ${display} | ${diff} | ${bugs} | ${review} | ${count} | ${snapIds}${more} |\n`;
|
|
564
560
|
});
|
|
561
|
+
if (truncated) {
|
|
562
|
+
output += `\n⚠️ Result may be incomplete — pagination could not be fully exhausted.\n`;
|
|
563
|
+
}
|
|
565
564
|
output += `\nUse \`percy_get_snapshot\` with a snapshot ID for full comparison details.\n`;
|
|
566
565
|
return { content: [{ type: "text", text: output }] };
|
|
567
566
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { percyGet } from "../../../lib/percy-api/percy-auth.js";
|
|
2
|
+
import { formatDiffPercent } from "../../../lib/percy-api/build-items.js";
|
|
2
3
|
export async function percyGetComparison(args, config) {
|
|
3
4
|
const response = await percyGet(`/comparisons/${args.comparison_id}`, config, {
|
|
4
5
|
include: [
|
|
@@ -25,8 +26,8 @@ export async function percyGetComparison(args, config) {
|
|
|
25
26
|
output += `| **Browser** | ${browserName} |\n`;
|
|
26
27
|
output += `| **Width** | ${attrs.width || "?"}px |\n`;
|
|
27
28
|
output += `| **State** | ${attrs.state || "?"} |\n`;
|
|
28
|
-
output += `| **Diff ratio** | ${
|
|
29
|
-
output += `| **AI diff ratio** | ${
|
|
29
|
+
output += `| **Diff ratio** | ${formatDiffPercent(attrs["diff-ratio"])} |\n`;
|
|
30
|
+
output += `| **AI diff ratio** | ${formatDiffPercent(attrs["ai-diff-ratio"])} |\n`;
|
|
30
31
|
output += `| **AI state** | ${attrs["ai-processing-state"] || "—"} |\n`;
|
|
31
32
|
output += `| **Potential bugs** | ${ai["total-potential-bugs"] ?? "—"} |\n`;
|
|
32
33
|
output += `| **AI visual diffs** | ${ai["total-ai-visual-diffs"] ?? "—"} |\n`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { percyGet } from "../../../lib/percy-api/percy-auth.js";
|
|
2
|
+
import { formatDiffPercent } from "../../../lib/percy-api/build-items.js";
|
|
2
3
|
export async function percyGetSnapshot(args, config) {
|
|
3
4
|
const response = await percyGet(`/snapshots/${args.snapshot_id}`, config, {
|
|
4
5
|
include: [
|
|
@@ -19,7 +20,7 @@ export async function percyGetSnapshot(args, config) {
|
|
|
19
20
|
}
|
|
20
21
|
output += `| Field | Value |\n|---|---|\n`;
|
|
21
22
|
output += `| **Review** | ${attrs["review-state"] || "—"} (${attrs["review-state-reason"] || "—"}) |\n`;
|
|
22
|
-
output += `| **Diff ratio** | ${
|
|
23
|
+
output += `| **Diff ratio** | ${formatDiffPercent(attrs["diff-ratio"])} |\n`;
|
|
23
24
|
output += `| **Test case** | ${attrs["test-case-name"] || "none"} |\n`;
|
|
24
25
|
output += `| **Comments** | ${attrs["total-open-comments"] ?? 0} |\n`;
|
|
25
26
|
output += `| **Layout** | ${attrs["enable-layout"] ? "enabled" : "disabled"} |\n`;
|
|
@@ -45,12 +46,8 @@ export async function percyGetSnapshot(args, config) {
|
|
|
45
46
|
const ca = c.attributes || {};
|
|
46
47
|
const browserId = c.relationships?.browser?.data?.id;
|
|
47
48
|
const browserName = browsers.get(browserId) || "?";
|
|
48
|
-
const diff = ca["diff-ratio"]
|
|
49
|
-
|
|
50
|
-
: "—";
|
|
51
|
-
const aiDiff = ca["ai-diff-ratio"] != null
|
|
52
|
-
? (ca["ai-diff-ratio"] * 100).toFixed(1) + "%"
|
|
53
|
-
: "—";
|
|
49
|
+
const diff = formatDiffPercent(ca["diff-ratio"]);
|
|
50
|
+
const aiDiff = formatDiffPercent(ca["ai-diff-ratio"]);
|
|
54
51
|
const aiState = ca["ai-processing-state"] || "—";
|
|
55
52
|
const bugs = ca["ai-details"]?.["total-potential-bugs"] ?? "—";
|
|
56
53
|
output += `| ${browserName} | ${ca.width || "?"}px | ${diff} | ${aiDiff} | ${aiState} | ${bugs} |\n`;
|
|
@@ -1,27 +1,40 @@
|
|
|
1
1
|
import { percyGet } from "../../../lib/percy-api/percy-auth.js";
|
|
2
|
+
import { fetchAllBuildItems, formatDiffPercent, CHANGED_CATEGORY_PARAMS, } from "../../../lib/percy-api/build-items.js";
|
|
2
3
|
export async function percySearchBuildItems(args, config) {
|
|
3
|
-
const params = {
|
|
4
|
-
|
|
4
|
+
const params = {
|
|
5
|
+
"filter[build-id]": args.build_id,
|
|
6
|
+
};
|
|
7
|
+
if (args.category === "changed") {
|
|
8
|
+
// The API resolves the changed category through subcategories; without
|
|
9
|
+
// them filter[category]=changed matches nothing.
|
|
10
|
+
Object.assign(params, CHANGED_CATEGORY_PARAMS);
|
|
11
|
+
}
|
|
12
|
+
else if (args.category) {
|
|
5
13
|
params["filter[category]"] = args.category;
|
|
14
|
+
}
|
|
6
15
|
if (args.sort_by)
|
|
7
16
|
params["filter[sort_by]"] = args.sort_by;
|
|
8
|
-
if (args.limit)
|
|
9
|
-
params["page[limit]"] = String(args.limit);
|
|
10
17
|
// Array filters
|
|
11
18
|
if (args.browser_ids)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
params["filter[browser_ids][]"] = args.browser_ids
|
|
20
|
+
.split(",")
|
|
21
|
+
.map((id) => id.trim());
|
|
15
22
|
if (args.widths)
|
|
16
|
-
args.widths.split(",").
|
|
17
|
-
params[`filter[widths][]`] = w.trim();
|
|
18
|
-
});
|
|
23
|
+
params["filter[widths][]"] = args.widths.split(",").map((w) => w.trim());
|
|
19
24
|
if (args.os)
|
|
20
25
|
params["filter[os]"] = args.os;
|
|
21
26
|
if (args.device_name)
|
|
22
27
|
params["filter[device_name]"] = args.device_name;
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
let items;
|
|
29
|
+
let truncated = false;
|
|
30
|
+
if (args.limit) {
|
|
31
|
+
params["page[limit]"] = String(args.limit);
|
|
32
|
+
const response = await percyGet("/build-items", config, params);
|
|
33
|
+
items = response?.data || [];
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
({ items, truncated } = await fetchAllBuildItems(config, params));
|
|
37
|
+
}
|
|
25
38
|
if (!items.length) {
|
|
26
39
|
return {
|
|
27
40
|
content: [
|
|
@@ -34,12 +47,13 @@ export async function percySearchBuildItems(args, config) {
|
|
|
34
47
|
items.forEach((item, i) => {
|
|
35
48
|
const attrs = item.attributes || item;
|
|
36
49
|
const name = attrs.coverSnapshotName || attrs["cover-snapshot-name"] || "?";
|
|
37
|
-
const diff = attrs.maxDiffRatio
|
|
38
|
-
? `${(attrs.maxDiffRatio * 100).toFixed(1)}%`
|
|
39
|
-
: "—";
|
|
50
|
+
const diff = formatDiffPercent(attrs.maxDiffRatio ?? attrs["max-diff-ratio"]);
|
|
40
51
|
const review = attrs.reviewState || attrs["review-state"] || "?";
|
|
41
52
|
const count = attrs.itemCount || attrs["item-count"] || 1;
|
|
42
53
|
output += `| ${i + 1} | ${name} | ${diff} | ${review} | ${count} |\n`;
|
|
43
54
|
});
|
|
55
|
+
if (truncated) {
|
|
56
|
+
output += `\n⚠️ Result may be incomplete — pagination could not be fully exhausted.\n`;
|
|
57
|
+
}
|
|
44
58
|
return { content: [{ type: "text", text: output }] };
|
|
45
59
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PercyClient } from "../../../lib/percy-api/client.js";
|
|
2
|
+
import { formatDiffPercent } from "../../../lib/percy-api/build-items.js";
|
|
2
3
|
export async function percyAutoTriage(args, config) {
|
|
3
4
|
const client = new PercyClient(config);
|
|
4
5
|
const noiseThreshold = args.noise_threshold ?? 0.005; // 0.5%
|
|
@@ -43,14 +44,14 @@ export async function percyAutoTriage(args, config) {
|
|
|
43
44
|
if (critical.length > 0) {
|
|
44
45
|
output += `### CRITICAL — Potential Bugs (${critical.length})\n`;
|
|
45
46
|
critical.forEach((e, i) => {
|
|
46
|
-
output += `${i + 1}. **${e.name}** — ${(e.diffRatio
|
|
47
|
+
output += `${i + 1}. **${e.name}** — ${formatDiffPercent(e.diffRatio)} diff, ${e.potentialBugs} bug(s)\n`;
|
|
47
48
|
});
|
|
48
49
|
output += "\n";
|
|
49
50
|
}
|
|
50
51
|
if (reviewRequired.length > 0) {
|
|
51
52
|
output += `### REVIEW REQUIRED (${reviewRequired.length})\n`;
|
|
52
53
|
reviewRequired.forEach((e, i) => {
|
|
53
|
-
output += `${i + 1}. **${e.name}** — ${(e.diffRatio
|
|
54
|
+
output += `${i + 1}. **${e.name}** — ${formatDiffPercent(e.diffRatio)} diff\n`;
|
|
54
55
|
});
|
|
55
56
|
output += "\n";
|
|
56
57
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PercyClient } from "../../../lib/percy-api/client.js";
|
|
2
|
+
import { formatDiffPercent } from "../../../lib/percy-api/build-items.js";
|
|
2
3
|
import { pollUntil } from "../../../lib/percy-api/polling.js";
|
|
3
4
|
export async function percyDiffExplain(args, config) {
|
|
4
5
|
const client = new PercyClient(config);
|
|
@@ -22,9 +23,9 @@ export async function percyDiffExplain(args, config) {
|
|
|
22
23
|
// Basic diff info
|
|
23
24
|
const diffRatio = comparison.diffRatio ?? 0;
|
|
24
25
|
const aiDiffRatio = comparison.aiDiffRatio;
|
|
25
|
-
output += `**Diff:** ${(diffRatio
|
|
26
|
+
output += `**Diff:** ${formatDiffPercent(diffRatio)}`;
|
|
26
27
|
if (aiDiffRatio !== null && aiDiffRatio !== undefined) {
|
|
27
|
-
output += ` | **AI Diff:** ${(aiDiffRatio
|
|
28
|
+
output += ` | **AI Diff:** ${formatDiffPercent(aiDiffRatio)}`;
|
|
28
29
|
const reduction = diffRatio > 0 ? ((1 - aiDiffRatio / diffRatio) * 100).toFixed(0) : "0";
|
|
29
30
|
output += ` (${reduction}% noise filtered)`;
|
|
30
31
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PercyClient } from "../../../lib/percy-api/client.js";
|
|
2
|
+
import { formatDiffPercent } from "../../../lib/percy-api/build-items.js";
|
|
2
3
|
import { percyCache } from "../../../lib/percy-api/cache.js";
|
|
3
4
|
import { formatBuild } from "../../../lib/percy-api/formatter.js";
|
|
4
5
|
export async function percyPrVisualReport(args, config) {
|
|
@@ -136,21 +137,21 @@ export async function percyPrVisualReport(args, config) {
|
|
|
136
137
|
if (critical.length > 0) {
|
|
137
138
|
output += `**CRITICAL — Potential Bugs (${critical.length}):**\n`;
|
|
138
139
|
critical.forEach((e, i) => {
|
|
139
|
-
output += `${i + 1}. **${e.name}** — ${(e.diffRatio
|
|
140
|
+
output += `${i + 1}. **${e.name}** — ${formatDiffPercent(e.diffRatio)} diff, ${e.potentialBugs} bug(s) flagged\n`;
|
|
140
141
|
});
|
|
141
142
|
output += "\n";
|
|
142
143
|
}
|
|
143
144
|
if (review.length > 0) {
|
|
144
145
|
output += `**REVIEW REQUIRED (${review.length}):**\n`;
|
|
145
146
|
review.forEach((e, i) => {
|
|
146
|
-
output += `${i + 1}. **${e.name}** — ${(e.diffRatio
|
|
147
|
+
output += `${i + 1}. **${e.name}** — ${formatDiffPercent(e.diffRatio)} diff\n`;
|
|
147
148
|
});
|
|
148
149
|
output += "\n";
|
|
149
150
|
}
|
|
150
151
|
if (expected.length > 0) {
|
|
151
152
|
output += `**EXPECTED CHANGES (${expected.length}):**\n`;
|
|
152
153
|
expected.forEach((e, i) => {
|
|
153
|
-
output += `${i + 1}. ${e.name} — ${(e.diffRatio
|
|
154
|
+
output += `${i + 1}. ${e.name} — ${formatDiffPercent(e.diffRatio)} diff\n`;
|
|
154
155
|
});
|
|
155
156
|
output += "\n";
|
|
156
157
|
}
|