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 +3 -1
- package/dist/audit.js +27 -1
- package/dist/client.d.ts +2 -0
- package/dist/client.js +9 -1
- package/package.json +1 -1
package/dist/audit.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
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
|
|
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.
|
|
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",
|