qaify-playwright-reporter 0.10.0 → 0.10.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/cli.js +2 -0
- package/dist/fix.d.ts +4 -0
- package/dist/fix.js +19 -9
- package/dist/usage.d.ts +1 -1
- package/dist/usage.js +13 -1
- package/package.json +51 -51
- package/templates/auto-fix.yml +6 -0
- package/templates/qaify-fix.yml +6 -0
package/dist/cli.js
CHANGED
|
@@ -210,6 +210,8 @@ switch (args.command) {
|
|
|
210
210
|
const problem = describeProblem(client);
|
|
211
211
|
if (problem)
|
|
212
212
|
warn(problem);
|
|
213
|
+
// step merah: tanpa ini agen & submit dilewati diam-diam dan run tetap hijau
|
|
214
|
+
process.exitCode = 1;
|
|
213
215
|
}
|
|
214
216
|
await writeOutputs({ ok: String(result.ok), model: result.model ?? '', mode: result.mode });
|
|
215
217
|
break;
|
package/dist/fix.d.ts
CHANGED
|
@@ -132,10 +132,14 @@ export interface FixContextResult {
|
|
|
132
132
|
model: string | null;
|
|
133
133
|
/** deps = langkah Claude dilewati, fix-deps yang memperbaiki. */
|
|
134
134
|
mode: 'claude' | 'deps';
|
|
135
|
+
/** Alasan gagal yang sudah dilaporkan ke Qaify (job submit tidak jalan bila ok=false). */
|
|
136
|
+
failure?: string;
|
|
135
137
|
}
|
|
136
138
|
/**
|
|
137
139
|
* GET {base}/{id}/context → clone repo di commit sumber (yang diaudit / run yang gagal) ke <dir>/repo → cabut token
|
|
138
140
|
* → <dir>/.qaify/prompt.txt. Branch perbaikan dibuat lokal; push dilakukan job submit.
|
|
141
|
+
* Konteks didapat tetapi model tidak valid / clone gagal → hasil "failed" langsung dikirim ke Qaify: job submit
|
|
142
|
+
* dilewati saat ok=false, dan tanpa ini perbaikan macet "running" bila webhook workflow_run tidak sampai.
|
|
139
143
|
*/
|
|
140
144
|
export declare function fixContext(client: IngestClient, fixId: string, dir: string, opts?: {
|
|
141
145
|
kind?: FixKind;
|
package/dist/fix.js
CHANGED
|
@@ -160,6 +160,8 @@ export function testFixPrompt(ctx) {
|
|
|
160
160
|
/**
|
|
161
161
|
* GET {base}/{id}/context → clone repo di commit sumber (yang diaudit / run yang gagal) ke <dir>/repo → cabut token
|
|
162
162
|
* → <dir>/.qaify/prompt.txt. Branch perbaikan dibuat lokal; push dilakukan job submit.
|
|
163
|
+
* Konteks didapat tetapi model tidak valid / clone gagal → hasil "failed" langsung dikirim ke Qaify: job submit
|
|
164
|
+
* dilewati saat ok=false, dan tanpa ini perbaikan macet "running" bila webhook workflow_run tidak sampai.
|
|
163
165
|
*/
|
|
164
166
|
export async function fixContext(client, fixId, dir, opts = {}) {
|
|
165
167
|
const kind = opts.kind ?? 'audit';
|
|
@@ -171,12 +173,18 @@ export async function fixContext(client, fixId, dir, opts = {}) {
|
|
|
171
173
|
log('qa-factory: konteks perbaikan tidak bisa diambil (perbaikan tidak dikenal / sudah selesai / token runner salah)');
|
|
172
174
|
return { ok: false, model: null, mode: 'claude' };
|
|
173
175
|
}
|
|
176
|
+
const fail = async (reason) => {
|
|
177
|
+
log(`qa-factory: ${reason}`);
|
|
178
|
+
await revokeTokens([ctx.repo.token], opts.fetch);
|
|
179
|
+
const res = await client.post(`${FIX_BASE[kind]}/${encodeURIComponent(fixId)}/result`, { status: 'failed', reason });
|
|
180
|
+
if (res)
|
|
181
|
+
log(`qa-factory: kegagalan dilaporkan ke Qaify (status ${res.status})`);
|
|
182
|
+
return { ok: false, model: null, mode: 'claude', failure: reason };
|
|
183
|
+
};
|
|
174
184
|
const deps = kind === 'audit' ? validTarget(ctx.deps) : null;
|
|
175
185
|
const dependency = kind === 'audit' ? (validTarget(ctx.dependency) ?? deps) : null;
|
|
176
186
|
if (!MODEL_PATTERN.test(ctx.model)) {
|
|
177
|
-
|
|
178
|
-
await revokeTokens([ctx.repo.token], opts.fetch);
|
|
179
|
-
return { ok: false, model: null, mode: 'claude' };
|
|
187
|
+
return fail(`Model "${String(ctx.model).slice(0, 100)}" tidak valid; pilih ulang model di Setelan → Agen AI.`);
|
|
180
188
|
}
|
|
181
189
|
if (process.env.GITHUB_ACTIONS === 'true')
|
|
182
190
|
console.log(`::add-mask::${ctx.repo.token}`);
|
|
@@ -189,9 +197,9 @@ export async function fixContext(client, fixId, dir, opts = {}) {
|
|
|
189
197
|
await exec('git', ['-C', target, 'checkout', '-q', '-b', ctx.repo.branch, 'FETCH_HEAD']);
|
|
190
198
|
}
|
|
191
199
|
catch (e) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
200
|
+
const message = String(e.message ?? e).split('\n')[0].replaceAll(ctx.repo.token, '***').slice(0, 500);
|
|
201
|
+
return fail(`Clone ${ctx.repo.full_name}@${String(ref).slice(0, 12)} gagal: ${message}. ` +
|
|
202
|
+
'Pastikan commit/branch itu masih ada di GitHub dan GitHub App Qaify terpasang di repo tersebut.');
|
|
195
203
|
}
|
|
196
204
|
await revokeTokens([ctx.repo.token], opts.fetch);
|
|
197
205
|
const { token: _token, ...repo } = ctx.repo;
|
|
@@ -430,9 +438,11 @@ export async function fixSubmit(client, fixId, claudeOutput, patch, workDir, opt
|
|
|
430
438
|
return send({
|
|
431
439
|
status: 'failed',
|
|
432
440
|
...(usage ? { usage } : {}),
|
|
433
|
-
reason:
|
|
434
|
-
? `
|
|
435
|
-
:
|
|
441
|
+
reason: usage?.error_code === 'ai_not_logged_in'
|
|
442
|
+
? `Claude Code belum login di runner (${agentError ?? 'tanpa kredensial'}): secret CLAUDE_CODE_OAUTH_TOKEN di repo runner kosong atau kedaluwarsa. Jalankan \`claude setup-token\` lalu isi secret itu, atau pakai API key di Setelan → Agen AI.`
|
|
443
|
+
: agentError
|
|
444
|
+
? `Agen AI gagal: ${agentError}`
|
|
445
|
+
: 'Agen AI tidak menghasilkan hasil perbaikan terstruktur. Cek log step "Perbaiki dengan Claude Code" / "Perbaiki dengan Qaify Agent" di workflow perbaikan: secret CLAUDE_CODE_OAUTH_TOKEN dan kuota langganan, atau API key, saldo & model di Setelan → Agen AI, atau batas giliran.',
|
|
436
446
|
});
|
|
437
447
|
}
|
|
438
448
|
const stats = patchStats(patch);
|
package/dist/usage.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface ClaudeUsage {
|
|
|
14
14
|
/** claude_code | qaify_agent (reporter ≥ 0.10; output Qaify Agent membawa engine & provider) */
|
|
15
15
|
engine?: string;
|
|
16
16
|
provider?: string;
|
|
17
|
-
/** ai_key_invalid | ai_credit_low | model_no_tools | max_turns | …: Qaify tidak mengeskalasi galat key / saldo */
|
|
17
|
+
/** ai_not_logged_in | ai_key_invalid | ai_credit_low | model_no_tools | max_turns | …: Qaify tidak mengeskalasi galat login / key / saldo */
|
|
18
18
|
error_code?: string;
|
|
19
19
|
}
|
|
20
20
|
/** Dari objek hasil `claude -p` yang sudah di-parse; null bila tidak ada data pemakaian sama sekali. */
|
package/dist/usage.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Sisa kuota resmi langganan tidak ada di output ini; yang tercatat hanya pemakaian per panggilan + tanda kena limit.
|
|
3
3
|
// "Claude AI usage limit reached|1762160400", "5-hour limit reached ∙ resets 3pm", "You've hit your limit · resets …"
|
|
4
4
|
const LIMIT_PATTERN = /usage limit|limit reached|hit your (?:usage )?limit|rate[ _-]?limit|resets? (?:at )?\d/i;
|
|
5
|
+
// Claude Code tanpa kredensial (secret CLAUDE_CODE_OAUTH_TOKEN kosong) / token OAuth kedaluwarsa atau dicabut
|
|
6
|
+
const NOT_LOGGED_IN = /not logged in|please run \/login|oauth token (?:has )?(?:expired|been revoked)|invalid oauth/i;
|
|
5
7
|
// API key (Claude Code + ANTHROPIC_API_KEY): key salah / saldo habis bukan limit langganan
|
|
6
8
|
const KEY_INVALID = /invalid (?:x-)?api[ _-]?key|authentication_error|invalid bearer token|unauthorized/i;
|
|
7
9
|
const CREDIT_LOW = /credit balance is too low|insufficient (?:credits|balance|quota)|billing/i;
|
|
@@ -25,7 +27,17 @@ export function parseClaudeUsage(parsed) {
|
|
|
25
27
|
// modelUsage: { "<model-id>": {...} }; ambil model dengan token keluaran terbanyak
|
|
26
28
|
const models = Object.entries((parsed.modelUsage ?? {}));
|
|
27
29
|
const model = models.sort(([, a], [, b]) => (b.outputTokens ?? 0) - (a.outputTokens ?? 0))[0]?.[0] ?? null;
|
|
28
|
-
const errorCode = typeof parsed.error_code === 'string'
|
|
30
|
+
const errorCode = typeof parsed.error_code === 'string'
|
|
31
|
+
? parsed.error_code
|
|
32
|
+
: !isError
|
|
33
|
+
? null
|
|
34
|
+
: NOT_LOGGED_IN.test(text)
|
|
35
|
+
? 'ai_not_logged_in'
|
|
36
|
+
: KEY_INVALID.test(text)
|
|
37
|
+
? 'ai_key_invalid'
|
|
38
|
+
: CREDIT_LOW.test(text)
|
|
39
|
+
? 'ai_credit_low'
|
|
40
|
+
: null;
|
|
29
41
|
return {
|
|
30
42
|
model,
|
|
31
43
|
input_tokens: Number(u?.input_tokens ?? 0) + Number(u?.cache_creation_input_tokens ?? 0) + Number(u?.cache_read_input_tokens ?? 0),
|
package/package.json
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "qaify-playwright-reporter",
|
|
3
|
-
"version": "0.10.
|
|
4
|
-
"description": "Reporter Playwright untuk QA Factory (Qaify): kirim hasil test dan artefak ke /api/ingest, tanpa pernah menggagalkan test.",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "ahmdkenz",
|
|
7
|
-
"homepage": "https://qaify.vercel.app",
|
|
8
|
-
"keywords": [
|
|
9
|
-
"playwright",
|
|
10
|
-
"reporter",
|
|
11
|
-
"qa",
|
|
12
|
-
"e2e",
|
|
13
|
-
"github-actions",
|
|
14
|
-
"claude"
|
|
15
|
-
],
|
|
16
|
-
"type": "module",
|
|
17
|
-
"main": "./dist/index.js",
|
|
18
|
-
"types": "./dist/index.d.ts",
|
|
19
|
-
"exports": {
|
|
20
|
-
".": {
|
|
21
|
-
"types": "./dist/index.d.ts",
|
|
22
|
-
"default": "./dist/index.js"
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
"bin": {
|
|
26
|
-
"qaify": "dist/cli.js",
|
|
27
|
-
"playwright-reporter": "dist/cli.js"
|
|
28
|
-
},
|
|
29
|
-
"files": [
|
|
30
|
-
"dist",
|
|
31
|
-
"templates"
|
|
32
|
-
],
|
|
33
|
-
"engines": {
|
|
34
|
-
"node": ">=20"
|
|
35
|
-
},
|
|
36
|
-
"scripts": {
|
|
37
|
-
"build": "tsc -p tsconfig.build.json",
|
|
38
|
-
"type-check": "tsc --noEmit",
|
|
39
|
-
"test": "vitest run",
|
|
40
|
-
"prepare": "npm run build"
|
|
41
|
-
},
|
|
42
|
-
"peerDependencies": {
|
|
43
|
-
"@playwright/test": ">=1.45"
|
|
44
|
-
},
|
|
45
|
-
"devDependencies": {
|
|
46
|
-
"@playwright/test": "^1.64.1",
|
|
47
|
-
"@types/node": "^24.13.4",
|
|
48
|
-
"typescript": "~6.0.3",
|
|
49
|
-
"vitest": "^4.1.11"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "qaify-playwright-reporter",
|
|
3
|
+
"version": "0.10.2",
|
|
4
|
+
"description": "Reporter Playwright untuk QA Factory (Qaify): kirim hasil test dan artefak ke /api/ingest, tanpa pernah menggagalkan test.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "ahmdkenz",
|
|
7
|
+
"homepage": "https://qaify.vercel.app",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"playwright",
|
|
10
|
+
"reporter",
|
|
11
|
+
"qa",
|
|
12
|
+
"e2e",
|
|
13
|
+
"github-actions",
|
|
14
|
+
"claude"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"qaify": "dist/cli.js",
|
|
27
|
+
"playwright-reporter": "dist/cli.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"templates"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc -p tsconfig.build.json",
|
|
38
|
+
"type-check": "tsc --noEmit",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"prepare": "npm run build"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@playwright/test": ">=1.45"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@playwright/test": "^1.64.1",
|
|
47
|
+
"@types/node": "^24.13.4",
|
|
48
|
+
"typescript": "~6.0.3",
|
|
49
|
+
"vitest": "^4.1.11"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/templates/auto-fix.yml
CHANGED
|
@@ -100,6 +100,12 @@ jobs:
|
|
|
100
100
|
MODEL: ${{ steps.ai.outputs.model || steps.ctx.outputs.model }}
|
|
101
101
|
CI: 'true'
|
|
102
102
|
run: |
|
|
103
|
+
# tanpa secret langganan dan tanpa API key: Claude Code pasti "Not logged in"; catat penyebabnya, jangan dipasang
|
|
104
|
+
if [ ! -f "$RUNNER_TEMP/qaify-ai.env" ] && [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]; then
|
|
105
|
+
echo '::error::Secret CLAUDE_CODE_OAUTH_TOKEN kosong di repo ini dan engineer tidak memakai API key. Jalankan "claude setup-token" lalu isi secret-nya, atau pakai API key di Qaify (Setelan, Agen AI).'
|
|
106
|
+
printf '%s' '{"type":"result","subtype":"error_qaify","is_error":true,"error_code":"ai_not_logged_in","result":"Not logged in (secret CLAUDE_CODE_OAUTH_TOKEN kosong)"}' > ../.qaify/claude.json
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
103
109
|
npm install -g @anthropic-ai/claude-code
|
|
104
110
|
# API key Anthropic dari Qaify menggantikan langganan
|
|
105
111
|
if [ -f "$RUNNER_TEMP/qaify-ai.env" ]; then
|
package/templates/qaify-fix.yml
CHANGED
|
@@ -100,6 +100,12 @@ jobs:
|
|
|
100
100
|
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
101
101
|
MODEL: ${{ steps.ai.outputs.model || steps.ctx.outputs.model }}
|
|
102
102
|
run: |
|
|
103
|
+
# tanpa secret langganan dan tanpa API key: Claude Code pasti "Not logged in"; catat penyebabnya, jangan dipasang
|
|
104
|
+
if [ ! -f "$RUNNER_TEMP/qaify-ai.env" ] && [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]; then
|
|
105
|
+
echo '::error::Secret CLAUDE_CODE_OAUTH_TOKEN kosong di repo runner ini dan engineer tidak memakai API key. Jalankan "claude setup-token" lalu isi secret-nya, atau pakai API key di Qaify (Setelan, Agen AI).'
|
|
106
|
+
printf '%s' '{"type":"result","subtype":"error_qaify","is_error":true,"error_code":"ai_not_logged_in","result":"Not logged in (secret CLAUDE_CODE_OAUTH_TOKEN kosong)"}' > ../.qaify/claude.json
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
103
109
|
npm install -g @anthropic-ai/claude-code
|
|
104
110
|
# API key Anthropic dari Qaify menggantikan langganan
|
|
105
111
|
if [ -f "$RUNNER_TEMP/qaify-ai.env" ]; then
|