@hasna/testers 0.0.26 → 0.0.28
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/index.js +127 -15
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -27119,7 +27119,7 @@ import chalk6 from "chalk";
|
|
|
27119
27119
|
// package.json
|
|
27120
27120
|
var package_default = {
|
|
27121
27121
|
name: "@hasna/testers",
|
|
27122
|
-
version: "0.0.
|
|
27122
|
+
version: "0.0.28",
|
|
27123
27123
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
27124
27124
|
type: "module",
|
|
27125
27125
|
main: "dist/index.js",
|
|
@@ -29607,12 +29607,48 @@ program2.command("results <run-id>").description("Show results for a test run").
|
|
|
29607
29607
|
process.exit(1);
|
|
29608
29608
|
}
|
|
29609
29609
|
});
|
|
29610
|
-
program2.command("screenshots <id>").description("List screenshots for a run or result").action((id) => {
|
|
29610
|
+
program2.command("screenshots <id>").description("List screenshots for a run or result").option("--json", "Output as JSON", false).option("-l, --limit <n>", "Limit results", "200").option("--offset <n>", "Skip first N results", "0").action((id, opts) => {
|
|
29611
29611
|
try {
|
|
29612
|
+
const limit = Math.max(1, parseInt(opts.limit, 10) || 200);
|
|
29613
|
+
const offset = Math.max(0, parseInt(opts.offset, 10) || 0);
|
|
29612
29614
|
const run = getRun(id);
|
|
29613
29615
|
if (run) {
|
|
29614
29616
|
const results = getResultsByRun(run.id);
|
|
29615
|
-
|
|
29617
|
+
const flattened = [];
|
|
29618
|
+
for (const result of results) {
|
|
29619
|
+
const screenshots2 = listScreenshots(result.id);
|
|
29620
|
+
const scenario = getScenario(result.scenarioId);
|
|
29621
|
+
for (const ss of screenshots2) {
|
|
29622
|
+
flattened.push({
|
|
29623
|
+
screenshotId: ss.id,
|
|
29624
|
+
resultId: result.id,
|
|
29625
|
+
scenarioId: result.scenarioId,
|
|
29626
|
+
scenarioShortId: scenario?.shortId ?? null,
|
|
29627
|
+
scenarioName: scenario?.name ?? null,
|
|
29628
|
+
stepNumber: ss.stepNumber,
|
|
29629
|
+
action: ss.action,
|
|
29630
|
+
filePath: ss.filePath,
|
|
29631
|
+
timestamp: ss.timestamp,
|
|
29632
|
+
width: ss.width,
|
|
29633
|
+
height: ss.height
|
|
29634
|
+
});
|
|
29635
|
+
}
|
|
29636
|
+
}
|
|
29637
|
+
const paged2 = flattened.slice(offset, offset + limit);
|
|
29638
|
+
if (opts.json) {
|
|
29639
|
+
log(JSON.stringify({
|
|
29640
|
+
input: id,
|
|
29641
|
+
type: "run",
|
|
29642
|
+
runId: run.id,
|
|
29643
|
+
total: flattened.length,
|
|
29644
|
+
limit,
|
|
29645
|
+
offset,
|
|
29646
|
+
items: paged2
|
|
29647
|
+
}, null, 2));
|
|
29648
|
+
return;
|
|
29649
|
+
}
|
|
29650
|
+
let seen = 0;
|
|
29651
|
+
let shown = 0;
|
|
29616
29652
|
log("");
|
|
29617
29653
|
log(chalk6.bold(` Screenshots for run ${run.id.slice(0, 8)}`));
|
|
29618
29654
|
log("");
|
|
@@ -29621,28 +29657,70 @@ program2.command("screenshots <id>").description("List screenshots for a run or
|
|
|
29621
29657
|
if (screenshots2.length > 0) {
|
|
29622
29658
|
const scenario = getScenario(result.scenarioId);
|
|
29623
29659
|
const label = scenario ? `${scenario.shortId}: ${scenario.name}` : result.scenarioId.slice(0, 8);
|
|
29624
|
-
|
|
29660
|
+
let sectionPrinted = false;
|
|
29625
29661
|
for (const ss of screenshots2) {
|
|
29662
|
+
if (seen < offset) {
|
|
29663
|
+
seen++;
|
|
29664
|
+
continue;
|
|
29665
|
+
}
|
|
29666
|
+
if (shown >= limit)
|
|
29667
|
+
break;
|
|
29668
|
+
if (!sectionPrinted) {
|
|
29669
|
+
log(chalk6.bold(` ${label}`));
|
|
29670
|
+
sectionPrinted = true;
|
|
29671
|
+
}
|
|
29626
29672
|
log(` ${chalk6.dim(String(ss.stepNumber).padStart(3, "0"))} ${ss.action} \u2014 ${chalk6.dim(ss.filePath)}`);
|
|
29627
|
-
|
|
29673
|
+
seen++;
|
|
29674
|
+
shown++;
|
|
29628
29675
|
}
|
|
29629
|
-
|
|
29676
|
+
if (sectionPrinted)
|
|
29677
|
+
log("");
|
|
29678
|
+
if (shown >= limit)
|
|
29679
|
+
break;
|
|
29630
29680
|
}
|
|
29631
29681
|
}
|
|
29632
|
-
if (
|
|
29682
|
+
if (flattened.length === 0 || shown === 0) {
|
|
29633
29683
|
log(chalk6.dim(" No screenshots found."));
|
|
29634
29684
|
log("");
|
|
29685
|
+
} else if (offset + shown < flattened.length) {
|
|
29686
|
+
log(chalk6.dim(` Showing ${shown} of ${flattened.length} screenshots (use --limit/--offset to paginate)`));
|
|
29687
|
+
log("");
|
|
29635
29688
|
}
|
|
29636
29689
|
return;
|
|
29637
29690
|
}
|
|
29638
29691
|
const screenshots = listScreenshots(id);
|
|
29692
|
+
const paged = screenshots.slice(offset, offset + limit);
|
|
29693
|
+
if (opts.json) {
|
|
29694
|
+
log(JSON.stringify({
|
|
29695
|
+
input: id,
|
|
29696
|
+
type: "result",
|
|
29697
|
+
resultId: id,
|
|
29698
|
+
total: screenshots.length,
|
|
29699
|
+
limit,
|
|
29700
|
+
offset,
|
|
29701
|
+
items: paged.map((ss) => ({
|
|
29702
|
+
screenshotId: ss.id,
|
|
29703
|
+
stepNumber: ss.stepNumber,
|
|
29704
|
+
action: ss.action,
|
|
29705
|
+
filePath: ss.filePath,
|
|
29706
|
+
timestamp: ss.timestamp,
|
|
29707
|
+
width: ss.width,
|
|
29708
|
+
height: ss.height
|
|
29709
|
+
}))
|
|
29710
|
+
}, null, 2));
|
|
29711
|
+
return;
|
|
29712
|
+
}
|
|
29639
29713
|
if (screenshots.length > 0) {
|
|
29640
29714
|
log("");
|
|
29641
29715
|
log(chalk6.bold(` Screenshots for result ${id.slice(0, 8)}`));
|
|
29642
29716
|
log("");
|
|
29643
|
-
for (const ss of
|
|
29717
|
+
for (const ss of paged) {
|
|
29644
29718
|
log(` ${chalk6.dim(String(ss.stepNumber).padStart(3, "0"))} ${ss.action} \u2014 ${chalk6.dim(ss.filePath)}`);
|
|
29645
29719
|
}
|
|
29720
|
+
if (paged.length < screenshots.length) {
|
|
29721
|
+
log("");
|
|
29722
|
+
log(chalk6.dim(` Showing ${paged.length} of ${screenshots.length} screenshots (use --limit/--offset to paginate)`));
|
|
29723
|
+
}
|
|
29646
29724
|
log("");
|
|
29647
29725
|
return;
|
|
29648
29726
|
}
|
|
@@ -29821,10 +29899,28 @@ projectCmd.command("create <name>").description("Create a new project").option("
|
|
|
29821
29899
|
process.exit(1);
|
|
29822
29900
|
}
|
|
29823
29901
|
});
|
|
29824
|
-
projectCmd.command("list").description("List all projects").action(() => {
|
|
29902
|
+
projectCmd.command("list").description("List all projects").option("--json", "Output as JSON", false).option("--search <text>", "Filter by project name/path (case-insensitive substring)").option("-l, --limit <n>", "Limit results", "100").option("--offset <n>", "Skip first N results", "0").action((opts) => {
|
|
29825
29903
|
try {
|
|
29826
|
-
const
|
|
29827
|
-
|
|
29904
|
+
const limit = Math.max(1, parseInt(opts.limit, 10) || 100);
|
|
29905
|
+
const offset = Math.max(0, parseInt(opts.offset, 10) || 0);
|
|
29906
|
+
const search = typeof opts.search === "string" && opts.search.trim().length > 0 ? opts.search.trim().toLowerCase() : null;
|
|
29907
|
+
const allProjects = listProjects();
|
|
29908
|
+
const filtered = search ? allProjects.filter((p) => {
|
|
29909
|
+
const name = p.name.toLowerCase();
|
|
29910
|
+
const path = (p.path ?? "").toLowerCase();
|
|
29911
|
+
return name.includes(search) || path.includes(search);
|
|
29912
|
+
}) : allProjects;
|
|
29913
|
+
const paged = filtered.slice(offset, offset + limit);
|
|
29914
|
+
if (opts.json) {
|
|
29915
|
+
log(JSON.stringify({
|
|
29916
|
+
total: filtered.length,
|
|
29917
|
+
limit,
|
|
29918
|
+
offset,
|
|
29919
|
+
items: paged
|
|
29920
|
+
}, null, 2));
|
|
29921
|
+
return;
|
|
29922
|
+
}
|
|
29923
|
+
if (filtered.length === 0) {
|
|
29828
29924
|
log(chalk6.dim("No projects found."));
|
|
29829
29925
|
return;
|
|
29830
29926
|
}
|
|
@@ -29833,16 +29929,20 @@ projectCmd.command("list").description("List all projects").action(() => {
|
|
|
29833
29929
|
log("");
|
|
29834
29930
|
log(` ${"ID".padEnd(38)} ${"Name".padEnd(24)} ${"Path".padEnd(30)} Created`);
|
|
29835
29931
|
log(` ${"\u2500".repeat(38)} ${"\u2500".repeat(24)} ${"\u2500".repeat(30)} ${"\u2500".repeat(20)}`);
|
|
29836
|
-
for (const p of
|
|
29932
|
+
for (const p of paged) {
|
|
29837
29933
|
log(` ${p.id.padEnd(38)} ${p.name.padEnd(24)} ${(p.path ?? chalk6.dim("\u2014")).toString().padEnd(30)} ${p.createdAt}`);
|
|
29838
29934
|
}
|
|
29935
|
+
if (offset + paged.length < filtered.length) {
|
|
29936
|
+
log("");
|
|
29937
|
+
log(chalk6.dim(` Showing ${paged.length} of ${filtered.length} projects (use --limit/--offset to paginate)`));
|
|
29938
|
+
}
|
|
29839
29939
|
log("");
|
|
29840
29940
|
} catch (error) {
|
|
29841
29941
|
logError(chalk6.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
29842
29942
|
process.exit(1);
|
|
29843
29943
|
}
|
|
29844
29944
|
});
|
|
29845
|
-
projectCmd.command("show <id>").description("Show project details").action((id) => {
|
|
29945
|
+
projectCmd.command("show <id>").description("Show project details").option("--json", "Output as JSON", false).action((id, opts) => {
|
|
29846
29946
|
try {
|
|
29847
29947
|
let project = getProject(id);
|
|
29848
29948
|
if (!project) {
|
|
@@ -29853,6 +29953,10 @@ projectCmd.command("show <id>").description("Show project details").action((id)
|
|
|
29853
29953
|
logError(chalk6.red(`Project not found: ${id}`));
|
|
29854
29954
|
process.exit(1);
|
|
29855
29955
|
}
|
|
29956
|
+
if (opts.json) {
|
|
29957
|
+
log(JSON.stringify(project, null, 2));
|
|
29958
|
+
return;
|
|
29959
|
+
}
|
|
29856
29960
|
log("");
|
|
29857
29961
|
log(chalk6.bold(` Project: ${project.name}`));
|
|
29858
29962
|
log(` ID: ${project.id}`);
|
|
@@ -29866,7 +29970,7 @@ projectCmd.command("show <id>").description("Show project details").action((id)
|
|
|
29866
29970
|
process.exit(1);
|
|
29867
29971
|
}
|
|
29868
29972
|
});
|
|
29869
|
-
projectCmd.command("use <name>").description("Set active project (find or create)").action((name) => {
|
|
29973
|
+
projectCmd.command("use <name>").description("Set active project (find or create)").option("--json", "Output as JSON", false).action((name, opts) => {
|
|
29870
29974
|
try {
|
|
29871
29975
|
const project = ensureProject(name, process.cwd());
|
|
29872
29976
|
if (!existsSync14(CONFIG_DIR5)) {
|
|
@@ -29880,6 +29984,10 @@ projectCmd.command("use <name>").description("Set active project (find or create
|
|
|
29880
29984
|
}
|
|
29881
29985
|
config.activeProject = project.id;
|
|
29882
29986
|
writeFileSync4(CONFIG_PATH3, JSON.stringify(config, null, 2), "utf-8");
|
|
29987
|
+
if (opts.json) {
|
|
29988
|
+
log(JSON.stringify({ activeProject: project.id, project }, null, 2));
|
|
29989
|
+
return;
|
|
29990
|
+
}
|
|
29883
29991
|
log(chalk6.green(`Active project set to ${chalk6.bold(project.name)} (${project.id})`));
|
|
29884
29992
|
} catch (error) {
|
|
29885
29993
|
logError(chalk6.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
@@ -30615,9 +30723,13 @@ envCmd.command("add <name>").description("Add a named environment").requiredOpti
|
|
|
30615
30723
|
process.exit(1);
|
|
30616
30724
|
}
|
|
30617
30725
|
});
|
|
30618
|
-
envCmd.command("list").description("List all environments").option("--project <id>", "Filter by project ID").action((opts) => {
|
|
30726
|
+
envCmd.command("list").description("List all environments").option("--project <id>", "Filter by project ID").option("--json", "Output as JSON", false).action((opts) => {
|
|
30619
30727
|
try {
|
|
30620
30728
|
const envs = listEnvironments(opts.project);
|
|
30729
|
+
if (opts.json) {
|
|
30730
|
+
log(JSON.stringify({ total: envs.length, items: envs }, null, 2));
|
|
30731
|
+
return;
|
|
30732
|
+
}
|
|
30621
30733
|
if (envs.length === 0) {
|
|
30622
30734
|
log(chalk6.dim("No environments configured. Add one with: testers env add <name> --url <url>"));
|
|
30623
30735
|
return;
|