@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.
Files changed (2) hide show
  1. package/dist/cli/index.js +127 -15
  2. 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.26",
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
- let total = 0;
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
- log(chalk6.bold(` ${label}`));
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
- total++;
29673
+ seen++;
29674
+ shown++;
29628
29675
  }
29629
- log("");
29676
+ if (sectionPrinted)
29677
+ log("");
29678
+ if (shown >= limit)
29679
+ break;
29630
29680
  }
29631
29681
  }
29632
- if (total === 0) {
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 screenshots) {
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 projects = listProjects();
29827
- if (projects.length === 0) {
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 projects) {
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/testers",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "AI-powered QA testing CLI — spawns cheap AI agents to test web apps with headless browsers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",