@hasna/testers 0.0.11 → 0.0.13
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/dashboard/dist/assets/{index-DyXKnBM8.css → index-PT-52SEY.css} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/cli/index.js +2110 -1469
- package/dist/db/agents.d.ts +2 -0
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/index.js +215 -117
- package/dist/lib/affected.d.ts +23 -0
- package/dist/lib/affected.d.ts.map +1 -0
- package/dist/lib/failure-pipeline.d.ts +20 -0
- package/dist/lib/failure-pipeline.d.ts.map +1 -0
- package/dist/lib/git-watch.d.ts +16 -0
- package/dist/lib/git-watch.d.ts.map +1 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/mcp/index.js +656 -261
- package/dist/server/index.js +138 -13
- package/package.json +1 -1
- package/dist/cli/index.d.ts +0 -3
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/mcp/index.d.ts +0 -3
- package/dist/mcp/index.d.ts.map +0 -1
- /package/dashboard/dist/assets/{index-jNG_Nd_Q.js → index-FZ9gzLaz.js} +0 -0
package/dist/server/index.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __returnValue = (v) => v;
|
|
5
|
+
function __exportSetter(name, newValue) {
|
|
6
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
7
|
+
}
|
|
4
8
|
var __export = (target, all) => {
|
|
5
9
|
for (var name in all)
|
|
6
10
|
__defProp(target, name, {
|
|
7
11
|
get: all[name],
|
|
8
12
|
enumerable: true,
|
|
9
13
|
configurable: true,
|
|
10
|
-
set: (
|
|
14
|
+
set: __exportSetter.bind(all, name)
|
|
11
15
|
});
|
|
12
16
|
};
|
|
13
17
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -118,7 +122,7 @@ function flowFromRow(row) {
|
|
|
118
122
|
updatedAt: row.updated_at
|
|
119
123
|
};
|
|
120
124
|
}
|
|
121
|
-
var MODEL_MAP, VersionConflictError, BrowserError, AIClientError, ScheduleNotFoundError, DependencyCycleError;
|
|
125
|
+
var MODEL_MAP, VersionConflictError, BrowserError, AIClientError, TodosConnectionError, ScheduleNotFoundError, DependencyCycleError;
|
|
122
126
|
var init_types = __esm(() => {
|
|
123
127
|
MODEL_MAP = {
|
|
124
128
|
quick: "claude-haiku-4-5-20251001",
|
|
@@ -143,6 +147,12 @@ var init_types = __esm(() => {
|
|
|
143
147
|
this.name = "AIClientError";
|
|
144
148
|
}
|
|
145
149
|
};
|
|
150
|
+
TodosConnectionError = class TodosConnectionError extends Error {
|
|
151
|
+
constructor(message) {
|
|
152
|
+
super(message);
|
|
153
|
+
this.name = "TodosConnectionError";
|
|
154
|
+
}
|
|
155
|
+
};
|
|
146
156
|
ScheduleNotFoundError = class ScheduleNotFoundError extends Error {
|
|
147
157
|
constructor(id) {
|
|
148
158
|
super(`Schedule not found: ${id}`);
|
|
@@ -736,9 +746,9 @@ var init_flows = __esm(() => {
|
|
|
736
746
|
});
|
|
737
747
|
|
|
738
748
|
// src/server/index.ts
|
|
739
|
-
import { existsSync as
|
|
740
|
-
import { join as
|
|
741
|
-
import { homedir as
|
|
749
|
+
import { existsSync as existsSync5 } from "fs";
|
|
750
|
+
import { join as join5 } from "path";
|
|
751
|
+
import { homedir as homedir5 } from "os";
|
|
742
752
|
|
|
743
753
|
// node_modules/zod/v3/external.js
|
|
744
754
|
var exports_external = {};
|
|
@@ -6281,6 +6291,119 @@ async function pushFailedRunToLogs(run, failedResults, scenarios) {
|
|
|
6281
6291
|
} catch {}
|
|
6282
6292
|
}
|
|
6283
6293
|
|
|
6294
|
+
// src/lib/todos-connector.ts
|
|
6295
|
+
import { Database as Database2 } from "bun:sqlite";
|
|
6296
|
+
import { existsSync as existsSync4 } from "fs";
|
|
6297
|
+
import { join as join4 } from "path";
|
|
6298
|
+
import { homedir as homedir4 } from "os";
|
|
6299
|
+
init_types();
|
|
6300
|
+
function resolveTodosDbPath() {
|
|
6301
|
+
const envPath = process.env["TODOS_DB_PATH"];
|
|
6302
|
+
if (envPath)
|
|
6303
|
+
return envPath;
|
|
6304
|
+
return join4(homedir4(), ".todos", "todos.db");
|
|
6305
|
+
}
|
|
6306
|
+
function connectToTodos() {
|
|
6307
|
+
const dbPath = resolveTodosDbPath();
|
|
6308
|
+
if (!existsSync4(dbPath)) {
|
|
6309
|
+
throw new TodosConnectionError(`Todos database not found at ${dbPath}. Install @hasna/todos or set TODOS_DB_PATH.`);
|
|
6310
|
+
}
|
|
6311
|
+
const db2 = new Database2(dbPath, { readonly: true });
|
|
6312
|
+
db2.exec("PRAGMA foreign_keys = ON");
|
|
6313
|
+
return db2;
|
|
6314
|
+
}
|
|
6315
|
+
|
|
6316
|
+
// src/lib/failure-pipeline.ts
|
|
6317
|
+
async function createFailureTasks(run, failedResults, scenarios) {
|
|
6318
|
+
if (failedResults.length === 0)
|
|
6319
|
+
return { created: 0, skipped: 0 };
|
|
6320
|
+
const projectId = process.env["TESTERS_TODOS_PROJECT_ID"];
|
|
6321
|
+
if (!projectId)
|
|
6322
|
+
return { created: 0, skipped: 0 };
|
|
6323
|
+
let db2 = null;
|
|
6324
|
+
try {
|
|
6325
|
+
db2 = connectToTodos();
|
|
6326
|
+
} catch {
|
|
6327
|
+
return { created: 0, skipped: 0 };
|
|
6328
|
+
}
|
|
6329
|
+
const scenarioMap = new Map(scenarios.map((s) => [s.id, s]));
|
|
6330
|
+
let created = 0;
|
|
6331
|
+
let skipped = 0;
|
|
6332
|
+
try {
|
|
6333
|
+
for (const result of failedResults) {
|
|
6334
|
+
const scenario = scenarioMap.get(result.scenarioId);
|
|
6335
|
+
const title = `BUG: [testers] ${scenario?.name ?? result.scenarioId} failed`;
|
|
6336
|
+
const existing = db2.query("SELECT id FROM tasks WHERE title = ? AND status NOT IN ('completed', 'cancelled') LIMIT 1").get(title);
|
|
6337
|
+
if (existing) {
|
|
6338
|
+
skipped++;
|
|
6339
|
+
continue;
|
|
6340
|
+
}
|
|
6341
|
+
const id = crypto.randomUUID();
|
|
6342
|
+
const now2 = new Date().toISOString();
|
|
6343
|
+
const description = [
|
|
6344
|
+
`Test failure detected by open-testers.`,
|
|
6345
|
+
``,
|
|
6346
|
+
`**Run:** ${run.id}`,
|
|
6347
|
+
`**URL:** ${run.url}`,
|
|
6348
|
+
`**Scenario:** ${scenario?.name ?? result.scenarioId}`,
|
|
6349
|
+
`**Status:** ${result.status}`,
|
|
6350
|
+
result.error ? `**Error:** ${result.error}` : null,
|
|
6351
|
+
result.reasoning ? `**Reasoning:** ${result.reasoning.slice(0, 500)}` : null,
|
|
6352
|
+
`**Duration:** ${result.durationMs ? `${(result.durationMs / 1000).toFixed(1)}s` : "N/A"}`,
|
|
6353
|
+
`**Tokens:** ${result.tokensUsed ?? 0}`
|
|
6354
|
+
].filter(Boolean).join(`
|
|
6355
|
+
`);
|
|
6356
|
+
try {
|
|
6357
|
+
db2.query(`
|
|
6358
|
+
INSERT INTO tasks (id, short_id, title, description, status, priority, tags, project_id, version, created_at, updated_at)
|
|
6359
|
+
VALUES (?, ?, ?, ?, 'pending', 'high', ?, ?, 1, ?, ?)
|
|
6360
|
+
`).run(id, `BUG-${id.slice(0, 6)}`, title, description, JSON.stringify(["bug", "testers", "auto-created"]), projectId, now2, now2);
|
|
6361
|
+
created++;
|
|
6362
|
+
} catch {
|
|
6363
|
+
skipped++;
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
} finally {
|
|
6367
|
+
db2.close();
|
|
6368
|
+
}
|
|
6369
|
+
return { created, skipped };
|
|
6370
|
+
}
|
|
6371
|
+
async function notifyFailureToConversations(run, failedResults, scenarios) {
|
|
6372
|
+
const baseUrl = process.env["TESTERS_CONVERSATIONS_URL"];
|
|
6373
|
+
const space = process.env["TESTERS_CONVERSATIONS_SPACE"];
|
|
6374
|
+
if (!baseUrl || !space)
|
|
6375
|
+
return;
|
|
6376
|
+
const scenarioMap = new Map(scenarios.map((s) => [s.id, s]));
|
|
6377
|
+
const total = run.total;
|
|
6378
|
+
const failedCount = failedResults.length;
|
|
6379
|
+
const passedCount = run.passed;
|
|
6380
|
+
const failureLines = failedResults.slice(0, 5).map((r) => {
|
|
6381
|
+
const name = scenarioMap.get(r.scenarioId)?.name ?? r.scenarioId;
|
|
6382
|
+
const err = r.error ? ` \u2014 ${r.error.slice(0, 120)}` : "";
|
|
6383
|
+
return ` \u274C ${name}${err}`;
|
|
6384
|
+
});
|
|
6385
|
+
const extra = failedResults.length > 5 ? ` \u2026 and ${failedResults.length - 5} more` : "";
|
|
6386
|
+
const message = [
|
|
6387
|
+
`\uD83D\uDEA8 **Testers run failed** \u2014 ${failedCount}/${total} scenarios failed`,
|
|
6388
|
+
``,
|
|
6389
|
+
`**URL:** ${run.url}`,
|
|
6390
|
+
`**Run ID:** \`${run.id}\``,
|
|
6391
|
+
`**Pass rate:** ${passedCount}/${total}`,
|
|
6392
|
+
``,
|
|
6393
|
+
`**Failures:**`,
|
|
6394
|
+
...failureLines,
|
|
6395
|
+
extra
|
|
6396
|
+
].filter((l) => l !== "").join(`
|
|
6397
|
+
`);
|
|
6398
|
+
try {
|
|
6399
|
+
await fetch(`${baseUrl.replace(/\/$/, "")}/api/spaces/${encodeURIComponent(space)}/messages`, {
|
|
6400
|
+
method: "POST",
|
|
6401
|
+
headers: { "Content-Type": "application/json" },
|
|
6402
|
+
body: JSON.stringify({ content: message, from: "testers" })
|
|
6403
|
+
});
|
|
6404
|
+
} catch {}
|
|
6405
|
+
}
|
|
6406
|
+
|
|
6284
6407
|
// src/lib/runner.ts
|
|
6285
6408
|
var eventHandler = null;
|
|
6286
6409
|
function emit(event) {
|
|
@@ -6514,6 +6637,8 @@ async function runBatch(scenarios, options) {
|
|
|
6514
6637
|
if (finalRun.status === "failed") {
|
|
6515
6638
|
const failedResults = results.filter((r) => r.status === "failed" || r.status === "error");
|
|
6516
6639
|
pushFailedRunToLogs(finalRun, failedResults, scenarios).catch(() => {});
|
|
6640
|
+
createFailureTasks(finalRun, failedResults, scenarios).catch(() => {});
|
|
6641
|
+
notifyFailureToConversations(finalRun, failedResults, scenarios).catch(() => {});
|
|
6517
6642
|
}
|
|
6518
6643
|
return { run: finalRun, results };
|
|
6519
6644
|
}
|
|
@@ -7003,7 +7128,7 @@ async function handleRequest(req) {
|
|
|
7003
7128
|
if (pathname === "/api/status" && method === "GET") {
|
|
7004
7129
|
const config = loadConfig();
|
|
7005
7130
|
getDatabase();
|
|
7006
|
-
const dbPath = process.env["TESTERS_DB_PATH"] ??
|
|
7131
|
+
const dbPath = process.env["TESTERS_DB_PATH"] ?? join5(homedir5(), ".testers", "testers.db");
|
|
7007
7132
|
const scenarios = listScenarios();
|
|
7008
7133
|
const runs = listRuns();
|
|
7009
7134
|
return jsonResponse({
|
|
@@ -7198,7 +7323,7 @@ async function handleRequest(req) {
|
|
|
7198
7323
|
const screenshot = getScreenshot(id);
|
|
7199
7324
|
if (!screenshot)
|
|
7200
7325
|
return errorResponse("Screenshot not found", 404);
|
|
7201
|
-
if (!
|
|
7326
|
+
if (!existsSync5(screenshot.filePath)) {
|
|
7202
7327
|
return errorResponse("Screenshot file not found on disk", 404);
|
|
7203
7328
|
}
|
|
7204
7329
|
const file = Bun.file(screenshot.filePath);
|
|
@@ -7253,8 +7378,8 @@ async function handleRequest(req) {
|
|
|
7253
7378
|
return jsonResponse({ deleted: true });
|
|
7254
7379
|
}
|
|
7255
7380
|
if (!pathname.startsWith("/api")) {
|
|
7256
|
-
const dashboardDir =
|
|
7257
|
-
if (!
|
|
7381
|
+
const dashboardDir = join5(import.meta.dir, "..", "..", "dashboard", "dist");
|
|
7382
|
+
if (!existsSync5(dashboardDir)) {
|
|
7258
7383
|
return new Response(`<!DOCTYPE html>
|
|
7259
7384
|
<html>
|
|
7260
7385
|
<head><title>Open Testers</title></head>
|
|
@@ -7272,8 +7397,8 @@ async function handleRequest(req) {
|
|
|
7272
7397
|
}
|
|
7273
7398
|
});
|
|
7274
7399
|
}
|
|
7275
|
-
const filePath =
|
|
7276
|
-
if (
|
|
7400
|
+
const filePath = join5(dashboardDir, pathname === "/" ? "index.html" : pathname);
|
|
7401
|
+
if (existsSync5(filePath)) {
|
|
7277
7402
|
const file = Bun.file(filePath);
|
|
7278
7403
|
return new Response(file, {
|
|
7279
7404
|
headers: {
|
|
@@ -7282,8 +7407,8 @@ async function handleRequest(req) {
|
|
|
7282
7407
|
}
|
|
7283
7408
|
});
|
|
7284
7409
|
}
|
|
7285
|
-
const indexPath =
|
|
7286
|
-
if (
|
|
7410
|
+
const indexPath = join5(dashboardDir, "index.html");
|
|
7411
|
+
if (existsSync5(indexPath)) {
|
|
7287
7412
|
const file = Bun.file(indexPath);
|
|
7288
7413
|
return new Response(file, {
|
|
7289
7414
|
headers: {
|
package/package.json
CHANGED
package/dist/cli/index.d.ts
DELETED
package/dist/cli/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.tsx"],"names":[],"mappings":""}
|
package/dist/mcp/index.d.ts
DELETED
package/dist/mcp/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":""}
|
|
File without changes
|