@sentinelqa/playwright-reporter 0.1.33 → 0.1.35

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/reporter.js CHANGED
@@ -2,6 +2,7 @@
2
2
  const node_1 = require("@sentinelqa/uploader/node");
3
3
  const env_1 = require("./env");
4
4
  const quickDiagnosis_1 = require("./quickDiagnosis");
5
+ const runHistory_1 = require("./runHistory");
5
6
  const { sentinelCaptureFailureContextFromReporter } = require("@sentinelqa/uploader/playwright");
6
7
  const colorize = (value, code) => {
7
8
  if (!process.stdout.isTTY)
@@ -43,19 +44,31 @@ class SentinelReporter {
43
44
  }
44
45
  }
45
46
  async onEnd() {
46
- console.log("");
47
- console.log(green("✔ Artifacts collected"));
47
+ const hasWorkspaceToken = Boolean(process.env.SENTINEL_TOKEN);
48
48
  const quickDiagnosis = (0, quickDiagnosis_1.buildQuickDiagnosis)(this.options.playwrightJsonPath);
49
+ const runDiff = (0, runHistory_1.buildRunDiffSummary)(this.options.playwrightJsonPath);
50
+ console.log("");
49
51
  if (quickDiagnosis?.lines.length) {
50
- console.log("");
51
52
  console.log(yellow("Quick diagnosis"));
52
53
  for (const line of quickDiagnosis.lines) {
53
54
  console.log(` ${dim(line)}`);
54
55
  }
56
+ console.log("");
57
+ }
58
+ if (runDiff) {
59
+ console.log(yellow("Run-to-run diff"));
60
+ console.log(` ${dim(`New failures: ${runDiff.newFailures}`)}`);
61
+ console.log(` ${dim(`Fixed since last run: ${runDiff.fixedTests}`)}`);
62
+ console.log(` ${dim(`Still failing: ${runDiff.stillFailing}`)}`);
63
+ console.log("");
64
+ }
65
+ if (hasWorkspaceToken) {
66
+ console.log("");
67
+ console.log(green("✔ Artifacts collected"));
68
+ console.log("");
69
+ console.log("Uploading hosted debugging report to Sentinel...");
70
+ console.log("");
55
71
  }
56
- console.log("");
57
- console.log("Uploading hosted debugging report to Sentinel...");
58
- console.log("");
59
72
  const upload = await (0, node_1.runSentinelUpload)({
60
73
  playwrightJsonPath: this.options.playwrightJsonPath,
61
74
  playwrightReportDir: this.options.playwrightReportDir,
@@ -71,14 +84,15 @@ class SentinelReporter {
71
84
  throw new Error(`Sentinel upload failed with exit code ${upload.exitCode}`);
72
85
  }
73
86
  console.log("");
74
- console.log(" Hosted report uploaded to Sentinel");
75
- if (upload.shareRunUrl || upload.internalRunUrl) {
87
+ console.log("Sentinel report");
88
+ console.log(` ${upload.shareRunUrl || upload.internalRunUrl}`);
89
+ if (upload.shareLabel) {
90
+ console.log(` ${dim(upload.shareLabel)}`);
91
+ }
92
+ if (!hasWorkspaceToken) {
76
93
  console.log("");
77
- console.log("Sentinel report");
78
- console.log(` ${upload.shareRunUrl || upload.internalRunUrl}`);
79
- if (upload.shareLabel) {
80
- console.log(` ${dim(upload.shareLabel)}`);
81
- }
94
+ console.log("Upgrade for free to get full AI debugging suggestions");
95
+ console.log(` ${dim("https://app.sentinelqa.com/register")}`);
82
96
  }
83
97
  }
84
98
  }
@@ -0,0 +1,7 @@
1
+ type RunDiffSummary = {
2
+ newFailures: number;
3
+ fixedTests: number;
4
+ stillFailing: number;
5
+ };
6
+ export declare const buildRunDiffSummary: (playwrightJsonPath: string) => RunDiffSummary | null;
7
+ export {};
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildRunDiffSummary = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const quickDiagnosis_1 = require("./quickDiagnosis");
10
+ const SENTINEL_HISTORY_DIR = node_path_1.default.join(".sentinel", "history");
11
+ const ensureDir = (dirPath) => {
12
+ if (!node_fs_1.default.existsSync(dirPath)) {
13
+ node_fs_1.default.mkdirSync(dirPath, { recursive: true });
14
+ }
15
+ };
16
+ const getCurrentBranch = () => {
17
+ const fromEnv = process.env.GITHUB_REF_NAME ||
18
+ process.env.CI_COMMIT_REF_NAME ||
19
+ process.env.CI_COMMIT_BRANCH ||
20
+ process.env.BRANCH_NAME ||
21
+ null;
22
+ return fromEnv && fromEnv.trim() ? fromEnv.trim() : "main";
23
+ };
24
+ const getCurrentGitSha = () => {
25
+ const fromEnv = process.env.GITHUB_SHA ||
26
+ process.env.CI_COMMIT_SHA ||
27
+ process.env.VERCEL_GIT_COMMIT_SHA ||
28
+ null;
29
+ return fromEnv && fromEnv.trim() ? fromEnv.trim() : "unknown";
30
+ };
31
+ const buildMatchKey = (failure) => [
32
+ failure.signal,
33
+ failure.locator || "unknown-locator",
34
+ failure.expected || "unknown-expected",
35
+ failure.received || "unknown-received",
36
+ failure.titlePath.join(" > ") || failure.title
37
+ ].join("|");
38
+ const buildSnapshot = (playwrightJsonPath) => {
39
+ const failures = (0, quickDiagnosis_1.collectFailureFacts)(playwrightJsonPath);
40
+ return {
41
+ generatedAt: new Date().toISOString(),
42
+ branch: getCurrentBranch(),
43
+ gitSha: getCurrentGitSha(),
44
+ failures: failures.map((failure) => ({
45
+ id: failure.titlePath.join(" > ") || failure.title,
46
+ matchKey: buildMatchKey(failure),
47
+ title: failure.title,
48
+ status: failure.status
49
+ }))
50
+ };
51
+ };
52
+ const getPointerPaths = (branch) => [
53
+ node_path_1.default.join(".sentinel", "latest.json"),
54
+ node_path_1.default.join(".sentinel", `latest-${branch}.json`),
55
+ ...(branch === "main" ? [node_path_1.default.join(".sentinel", "latest-main.json")] : [])
56
+ ];
57
+ const normalizeFailures = (snapshot) => {
58
+ if (!snapshot)
59
+ return [];
60
+ if (Array.isArray(snapshot.failures)) {
61
+ return snapshot.failures.filter((failure) => Boolean(failure &&
62
+ typeof failure.id === "string" &&
63
+ typeof failure.matchKey === "string" &&
64
+ typeof failure.title === "string" &&
65
+ typeof failure.status === "string"));
66
+ }
67
+ if (Array.isArray(snapshot.tests)) {
68
+ return snapshot.tests
69
+ .filter((test) => test && typeof test.id === "string")
70
+ .map((test) => ({
71
+ id: test.id,
72
+ matchKey: typeof test.matchKey === "string" ? test.matchKey : test.id,
73
+ title: typeof test.title === "string" ? test.title : test.id,
74
+ status: typeof test.status === "string" ? test.status : "failed"
75
+ }));
76
+ }
77
+ return [];
78
+ };
79
+ const readSnapshot = (filePath) => {
80
+ if (!node_fs_1.default.existsSync(filePath))
81
+ return null;
82
+ try {
83
+ return JSON.parse(node_fs_1.default.readFileSync(filePath, "utf8"));
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ };
89
+ const writeSnapshot = (snapshot) => {
90
+ ensureDir(node_path_1.default.resolve(process.cwd(), ".sentinel"));
91
+ ensureDir(node_path_1.default.resolve(process.cwd(), SENTINEL_HISTORY_DIR));
92
+ const fileName = `${snapshot.generatedAt.replace(/[:.]/g, "-")}-${snapshot.gitSha}.json`;
93
+ const historyPath = node_path_1.default.resolve(process.cwd(), SENTINEL_HISTORY_DIR, fileName);
94
+ node_fs_1.default.writeFileSync(historyPath, JSON.stringify(snapshot, null, 2), "utf8");
95
+ for (const pointerPath of getPointerPaths(snapshot.branch)) {
96
+ node_fs_1.default.writeFileSync(node_path_1.default.resolve(process.cwd(), pointerPath), JSON.stringify({ path: historyPath, ...snapshot }, null, 2), "utf8");
97
+ }
98
+ };
99
+ const buildRunDiffSummary = (playwrightJsonPath) => {
100
+ const snapshot = buildSnapshot(playwrightJsonPath);
101
+ const previous = readSnapshot(node_path_1.default.resolve(process.cwd(), ".sentinel", `latest-${snapshot.branch}.json`)) ||
102
+ readSnapshot(node_path_1.default.resolve(process.cwd(), ".sentinel", "latest.json"));
103
+ const previousFailures = normalizeFailures(previous);
104
+ const currentFailureIds = new Set(snapshot.failures.map((test) => test.id));
105
+ const currentFailureMatchKeys = new Set(snapshot.failures.map((test) => test.matchKey));
106
+ const diff = previous && previous.generatedAt !== snapshot.generatedAt
107
+ ? {
108
+ newFailures: snapshot.failures.filter((test) => !previousFailures.some((prev) => prev.id === test.id || prev.matchKey === test.matchKey)).length,
109
+ fixedTests: previousFailures.filter((test) => !currentFailureIds.has(test.id) && !currentFailureMatchKeys.has(test.matchKey)).length,
110
+ stillFailing: snapshot.failures.filter((test) => previousFailures.some((prev) => prev.id === test.id || prev.matchKey === test.matchKey)).length
111
+ }
112
+ : null;
113
+ writeSnapshot(snapshot);
114
+ return diff;
115
+ };
116
+ exports.buildRunDiffSummary = buildRunDiffSummary;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentinelqa/playwright-reporter",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "private": false,
5
5
  "description": "Playwright reporter for CI debugging with optional Sentinel cloud dashboards",
6
6
  "license": "MIT",