@kinora/reporter 0.1.3 → 0.2.0

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/README.md CHANGED
@@ -51,11 +51,37 @@ Create an API token in the kinora dashboard (Settings → Workspace). Self-hosti
51
51
  | `project` | `{ slug: string, name?: string }` | required | Target project. `name` defaults to `slug`. |
52
52
  | `url` | `string` | env `KINORA_URL`, then cloud | kinora server base URL. Set for self-host. |
53
53
  | `token` | `string` | env `KINORA_TOKEN` | Project API token. Prefer the env var. |
54
- | `git` | `{ sha?, branch?, repoUrl? }` | auto on GitHub Actions | Git metadata. `repoUrl` links a sha to its commit. |
54
+ | `git` | `{ sha?, branch?, baseBranch?, repoUrl? }` | auto on GitHub Actions | Git metadata. `repoUrl` links a sha to its commit; `baseBranch` powers "regression vs base" in the PR comment. |
55
55
  | `ci` | `{ provider?, runUrl?, runNumber? }` | auto on GitHub Actions | CI metadata for the run. |
56
+ | `prComment` | `boolean \| { label?, policy? }` | off | Post/update a summary comment on the GitHub PR (see below). |
56
57
 
57
58
  On GitHub Actions, `git` and `ci` are filled from the standard `GITHUB_*` env vars (including the repo URL, so shas link to their commit in the dashboard). Pass them explicitly on other CI providers.
58
59
 
60
+ ## GitHub PR comment
61
+
62
+ On a `pull_request` run, the reporter can post (and keep updating) a summary comment on the PR: pass/fail counts, tests newly failing vs the base branch, and a link to the run. It uses the job's ambient `GITHUB_TOKEN`, so no credentials are stored in kinora.
63
+
64
+ ```ts
65
+ reporter: [['@kinora/reporter', { project: { slug: 'web-app' }, prComment: true }]]
66
+ ```
67
+
68
+ The workflow must grant write access to PRs:
69
+
70
+ ```yaml
71
+ # in your workflow job:
72
+ permissions:
73
+ pull-requests: write # required for the PR comment
74
+ steps:
75
+ - run: npx playwright test
76
+ env:
77
+ KINORA_TOKEN: ${{ secrets.KINORA_TOKEN }}
78
+ ```
79
+
80
+ - Same-repo PRs only: `GITHUB_TOKEN` is read-only on fork PRs, so the comment is skipped there.
81
+ - Shard your run with `merge-reports` (blob report) so one merged run posts one comment; per-shard runs are skipped.
82
+ - Matrix builds sharing a PR: set `prComment: { label: 'node20' }` so each leg keeps its own comment.
83
+ - `policy: 'on-failure'` skips the comment on green runs (default posts always).
84
+
59
85
  ## CI example (GitHub Actions)
60
86
 
61
87
  ```yaml
package/dist/index.d.mts CHANGED
@@ -14,6 +14,14 @@ interface KinoraReporterOptions {
14
14
  };
15
15
  git?: GitMeta;
16
16
  ci?: CiMeta;
17
+ /**
18
+ * Post/update a summary comment on the GitHub PR (uses the job's GITHUB_TOKEN; needs
19
+ * `permissions: pull-requests: write`). `label` distinguishes matrix legs sharing one PR.
20
+ */
21
+ prComment?: boolean | {
22
+ label?: string;
23
+ policy?: 'always' | 'on-failure';
24
+ };
17
25
  }
18
26
  declare class KinoraReporter implements Reporter {
19
27
  private readonly options;
@@ -22,6 +30,7 @@ declare class KinoraReporter implements Reporter {
22
30
  constructor(options: KinoraReporterOptions);
23
31
  onBegin(config: FullConfig, suite: Suite): void;
24
32
  onEnd(result: FullResult): Promise<void>;
33
+ private maybePostPrComment;
25
34
  }
26
35
  //#endregion
27
36
  export { KinoraReporterOptions, KinoraReporter as default };
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
+ import { readFileSync } from "node:fs";
1
2
  import { readFile } from "node:fs/promises";
2
3
  import process from "node:process";
3
- import { DEFAULT_KINORA_URL, IngestError, createIngestClient, effectiveAttachments, isTraceAttachment, makeTestKey } from "@kinora/core";
4
+ import { DEFAULT_KINORA_URL, IngestError, createIngestClient, effectiveAttachments, isTraceAttachment, makeTestKey, postPrComment, resolvePrContext } from "@kinora/core";
4
5
  //#region src/index.ts
5
6
  function identity(test) {
6
7
  let file = "";
@@ -70,12 +71,14 @@ function countsOf(tests) {
70
71
  function detectGit() {
71
72
  const sha = process.env.GITHUB_SHA;
72
73
  const branch = process.env.GITHUB_REF_NAME;
74
+ const baseBranch = process.env.GITHUB_BASE_REF || void 0;
73
75
  const { GITHUB_SERVER_URL, GITHUB_REPOSITORY } = process.env;
74
76
  const repoUrl = GITHUB_SERVER_URL && GITHUB_REPOSITORY ? `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}` : void 0;
75
77
  if (!sha && !branch && !repoUrl) return void 0;
76
78
  return {
77
79
  sha,
78
80
  branch,
81
+ baseBranch,
79
82
  repoUrl
80
83
  };
81
84
  }
@@ -125,7 +128,8 @@ var KinoraReporter = class {
125
128
  try {
126
129
  const client = createIngestClient({
127
130
  baseUrl: url,
128
- token
131
+ token,
132
+ regression: !!this.options.prComment
129
133
  });
130
134
  const res = await client.uploadRun(payload);
131
135
  let traces = 0;
@@ -145,11 +149,43 @@ var KinoraReporter = class {
145
149
  }
146
150
  }
147
151
  console.log(`[kinora] uploaded ${res.tests} tests + ${traces} traces (run ${res.runId})`);
152
+ await this.maybePostPrComment(payload, res);
148
153
  } catch (err) {
149
154
  if (err instanceof IngestError && err.status === 402) console.warn(`[kinora] ${err.message}`);
150
155
  else console.error(`[kinora] upload failed:`, err instanceof Error ? err.message : err);
151
156
  }
152
157
  }
158
+ async maybePostPrComment(payload, res) {
159
+ if (!this.options.prComment) return;
160
+ const opt = this.options.prComment;
161
+ const label = typeof opt === "object" ? opt.label : void 0;
162
+ const policy = typeof opt === "object" ? opt.policy : void 0;
163
+ try {
164
+ const ctx = resolvePrContext(this.config?.shard ? {
165
+ ...process.env,
166
+ __KINORA_IS_SHARD: "1"
167
+ } : process.env, (p) => {
168
+ try {
169
+ return readFileSync(p, "utf8");
170
+ } catch {
171
+ return;
172
+ }
173
+ });
174
+ if (!ctx) return;
175
+ const outcome = await postPrComment(ctx, {
176
+ projectSlug: payload.project.slug,
177
+ projectName: payload.project.name,
178
+ label,
179
+ runUrl: res.runUrl,
180
+ ciRunUrl: payload.run.ci?.runUrl,
181
+ counts: payload.run.counts,
182
+ regression: res.regression
183
+ }, policy ?? "always");
184
+ if (outcome !== "skipped") console.warn(`[kinora] PR comment ${outcome}`);
185
+ } catch (err) {
186
+ console.warn("[kinora] PR comment failed:", err instanceof Error ? err.message : err);
187
+ }
188
+ }
153
189
  };
154
190
  //#endregion
155
191
  export { KinoraReporter as default };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kinora/reporter",
3
3
  "type": "module",
4
- "version": "0.1.3",
4
+ "version": "0.2.0",
5
5
  "description": "Playwright reporter that uploads test results to a kinora server",
6
6
  "author": "Joris Gallot",
7
7
  "license": "MIT",
@@ -36,16 +36,19 @@
36
36
  "@playwright/test": ">=1.40.0"
37
37
  },
38
38
  "dependencies": {
39
- "@kinora/core": "0.1.3"
39
+ "@kinora/core": "0.2.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@playwright/test": "^1.61.0",
43
43
  "@types/node": "^26.0.0",
44
+ "@vitest/coverage-v8": "^4.1.9",
44
45
  "tsdown": "^0.22.3",
45
- "typescript": "~6.0.3"
46
+ "typescript": "~6.0.3",
47
+ "vitest": "^4.1.9"
46
48
  },
47
49
  "scripts": {
48
50
  "build": "tsdown",
49
- "typecheck": "tsc --noEmit"
51
+ "typecheck": "tsc --noEmit",
52
+ "test": "vitest run --coverage"
50
53
  }
51
54
  }