qaify-playwright-reporter 0.4.1 → 0.4.2

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/audit.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { IngestClient } from './client.js';
1
+ import { type IngestClient } from './client.js';
2
2
  export declare const AREAS: readonly ["ui", "business_logic", "api_contract", "validation", "error_handling", "security", "performance"];
3
3
  export declare const SEVERITIES: readonly ["critical", "high", "medium", "low"];
4
4
  export declare const ROLES: readonly ["fe", "be"];
@@ -295,5 +295,7 @@ export declare function auditContext(client: IngestClient, auditId: string, dir:
295
295
  * Screenshot (temuan + galeri halaman) hanya diambil dari dalam folder evidence (path di luar folder itu diabaikan).
296
296
  */
297
297
  export declare function auditSubmit(client: IngestClient, auditId: string, claudeOutput: string, evidenceDir: string, commits: Record<string, string>, log?: (msg: string) => void): Promise<any>;
298
+ /** Penyebab screenshot tidak terunggah, agar laporan "Tanpa screenshot" tidak terjadi diam-diam. */
299
+ export declare function uploadProblems(res: any, unsigned: number, rejected: number, putError: string | null): string[];
298
300
  /** Commit per role dari context.json (dibuat audit-context). */
299
301
  export declare function readCommits(contextFile: string): Promise<Record<string, string>>;
package/dist/audit.js CHANGED
@@ -6,6 +6,7 @@ import { execFile } from 'node:child_process';
6
6
  import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
7
7
  import { extname, join, resolve, sep } from 'node:path';
8
8
  import { promisify } from 'node:util';
9
+ import { warn } from './client.js';
9
10
  export const AREAS = ['ui', 'business_logic', 'api_contract', 'validation', 'error_handling', 'security', 'performance'];
10
11
  export const SEVERITIES = ['critical', 'high', 'medium', 'low'];
11
12
  export const ROLES = ['fe', 'be'];
@@ -304,15 +305,40 @@ async function uploadScreenshots(client, base, refs, evidenceDir, log) {
304
305
  const entries = [...files.entries()];
305
306
  const res = await client.post(`${base}/artifacts`, { items: entries.map(([, f]) => ({ mime: f.mime, size_bytes: f.size })) });
306
307
  const slots = Array.isArray(res?.uploads) ? res.uploads : [];
308
+ let unsigned = 0;
309
+ let rejected = 0;
307
310
  for (const [i, [ref, file]] of entries.entries()) {
308
311
  const slot = slots[i];
309
- if (slot?.url && (await client.put(slot.url, slot.headers ?? {}, await readFile(file.path))))
312
+ if (!slot?.url)
313
+ unsigned++;
314
+ else if (await client.put(slot.url, slot.headers ?? {}, await readFile(file.path)))
310
315
  uploaded.set(ref, slot.path);
316
+ else
317
+ rejected++;
311
318
  }
312
319
  log(`qa-factory: ${uploaded.size}/${files.size} screenshot terunggah`);
320
+ for (const problem of uploadProblems(res, unsigned, rejected, client.uploadError()))
321
+ warn(problem);
313
322
  }
314
323
  return uploaded;
315
324
  }
325
+ /** Penyebab screenshot tidak terunggah, agar laporan "Tanpa screenshot" tidak terjadi diam-diam. */
326
+ export function uploadProblems(res, unsigned, rejected, putError) {
327
+ const problems = [];
328
+ if (unsigned) {
329
+ problems.push(!res
330
+ ? `${unsigned} screenshot tidak diunggah: API Qaify menolak atau tidak menjawab permintaan URL upload.`
331
+ : res.reason === 'storage_not_configured'
332
+ ? `${unsigned} screenshot tidak diunggah: API Qaify tidak bisa menandatangani upload. Isi ARTIFACT_DISK dan AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_BUCKET / AWS_ENDPOINT (R2) di env API, lalu deploy ulang.`
333
+ : res.reason === 'audit_inactive'
334
+ ? `${unsigned} screenshot tidak diunggah: audit sudah tidak aktif (dibatalkan atau melewati batas waktu) saat hasil dikirim.`
335
+ : `${unsigned} screenshot tidak mendapat URL upload (format bukan PNG/JPEG atau melebihi batas ukuran).`);
336
+ }
337
+ if (rejected) {
338
+ problems.push(`${rejected} screenshot ditolak storage (${putError ?? 'gagal'}). Cek izin token R2 (Object Read & Write) serta AWS_ENDPOINT / AWS_BUCKET di env API.`);
339
+ }
340
+ return problems;
341
+ }
316
342
  /** Commit per role dari context.json (dibuat audit-context). */
317
343
  export async function readCommits(contextFile) {
318
344
  try {
package/dist/client.d.ts CHANGED
@@ -31,6 +31,8 @@ export interface IngestClient {
31
31
  post(path: string, body: unknown): Promise<any>;
32
32
  get(path: string): Promise<any>;
33
33
  put(url: string, headers: Record<string, string>, body: Uint8Array): Promise<boolean>;
34
+ /** Penyebab PUT terakhir yang gagal (mis. "HTTP 403 AccessDenied"), null bila belum pernah gagal. */
35
+ uploadError(): string | null;
34
36
  }
35
37
  export declare const sleep: (ms: number) => Promise<void>;
36
38
  export declare const SETTINGS_HINT = "Cek Settings \u2192 Secrets and variables \u2192 Actions: QA_FACTORY_TOKEN di tab Secrets, QA_FACTORY_URL di tab Variables.";
package/dist/client.js CHANGED
@@ -56,6 +56,7 @@ export function createClient(config = {}) {
56
56
  let problem = null;
57
57
  let httpStatus = null;
58
58
  let notFound = 0;
59
+ let uploadError = null;
59
60
  async function send(method, path, body) {
60
61
  if (!url || !token)
61
62
  return null;
@@ -109,11 +110,18 @@ export function createClient(config = {}) {
109
110
  async put(target, headers, body) {
110
111
  try {
111
112
  const res = await doFetch(target, { method: 'PUT', headers, body, signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS) });
113
+ if (!res.ok) {
114
+ // S3/R2 menjelaskan penolakan di <Code> (AccessDenied, SignatureDoesNotMatch, NoSuchBucket, …)
115
+ const code = /<Code>([^<]+)<\/Code>/.exec(await res.text().catch(() => ''))?.[1];
116
+ uploadError = `HTTP ${res.status}${code ? ` ${code}` : ''}`;
117
+ }
112
118
  return res.ok;
113
119
  }
114
- catch {
120
+ catch (e) {
121
+ uploadError = String(e?.cause ?? e?.message ?? e);
115
122
  return false;
116
123
  }
117
124
  },
125
+ uploadError: () => uploadError,
118
126
  };
119
127
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qaify-playwright-reporter",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Reporter Playwright untuk QA Factory (Qaify): kirim hasil test dan artefak ke /api/ingest, tanpa pernah menggagalkan test.",
5
5
  "license": "MIT",
6
6
  "author": "ahmdkenz",