@sudobility/testomniac_runner 0.0.130 → 0.0.131

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/bun.lock CHANGED
@@ -8,8 +8,8 @@
8
8
  "@noble/curves": "^1.0.0",
9
9
  "@noble/hashes": "^1.0.0",
10
10
  "@sudobility/signic_sdk": "^0.1.7",
11
- "@sudobility/testomniac_runner_service": "^0.1.128",
12
- "@sudobility/testomniac_types": "^0.0.67",
11
+ "@sudobility/testomniac_runner_service": "^0.1.130",
12
+ "@sudobility/testomniac_types": "^0.0.68",
13
13
  "hono": "^4.7.0",
14
14
  "jose": "^6.1.2",
15
15
  "openai": "^6.7.0",
@@ -172,9 +172,9 @@
172
172
 
173
173
  "@sudobility/signic_sdk": ["@sudobility/signic_sdk@0.1.7", "", {}, "sha512-5XSgHSVsmyrMQ/ui1nDywwzt9dbRCsaeJ5tX6mKw2ZXbTZ82OsMr+dqDyV9XV3pfy7IHRIZq73af5KBamx72Fw=="],
174
174
 
175
- "@sudobility/testomniac_runner_service": ["@sudobility/testomniac_runner_service@0.1.128", "", { "peerDependencies": { "@sudobility/testomniac_types": "^0.0.67", "openai": ">=6.0.0", "react": ">=18.0.0" }, "optionalPeers": ["openai", "react"] }, "sha512-YWFjcMmZROSHSNUZLt9czUJeWoYmIj69JCXUP9CD9sw5/UTzFFLwIdkqtAE2lG4RDo/Yb/5heZPmffZ1EfPPqw=="],
175
+ "@sudobility/testomniac_runner_service": ["@sudobility/testomniac_runner_service@0.1.130", "", { "peerDependencies": { "@sudobility/testomniac_types": "^0.0.68", "openai": ">=6.0.0", "react": ">=18.0.0" }, "optionalPeers": ["openai", "react"] }, "sha512-C6I/8Zo/ZqBrOMXeiWqaAKEJIEdRI6xWvtRQoyYEtxuh51WN8CtbVyhPn5AEzDQJQTWa+RJllo9u0SxW0L/o1g=="],
176
176
 
177
- "@sudobility/testomniac_types": ["@sudobility/testomniac_types@0.0.67", "", { "peerDependencies": { "@sudobility/types": "^1.9.62" } }, "sha512-kNBDk7AsFx1rqUWLq4nx0nf1alV9G7U5xbxj8qDvZKa+cCnUSiZFRtDB88ENWQzeIPVJAU8MaVo5yUKVL46eYg=="],
177
+ "@sudobility/testomniac_types": ["@sudobility/testomniac_types@0.0.68", "", { "peerDependencies": { "@sudobility/types": "^1.9.62" } }, "sha512-io3qbAuOLuCrjC4txkD+B21n/RGrfz2MH+W0XciAOkaxcfXagdHRUdWie/520HeItC9F91jP9SA67YAcx5aZ0A=="],
178
178
 
179
179
  "@sudobility/types": ["@sudobility/types@1.9.61", "", {}, "sha512-SODGpstB/iKfK3H/4BvJx/FBcc1h3gutUjGotyxN19VnOfWyzaDoEmW7eyoxOAYhZyXMXagSiii+NIEZvuxKog=="],
180
180
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sudobility/testomniac_runner",
3
- "version": "0.0.130",
3
+ "version": "0.0.131",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -24,8 +24,8 @@
24
24
  "@noble/curves": "^1.0.0",
25
25
  "@noble/hashes": "^1.0.0",
26
26
  "@sudobility/signic_sdk": "^0.1.7",
27
- "@sudobility/testomniac_runner_service": "^0.1.128",
28
- "@sudobility/testomniac_types": "^0.0.67",
27
+ "@sudobility/testomniac_runner_service": "^0.1.130",
28
+ "@sudobility/testomniac_types": "^0.0.68",
29
29
  "hono": "^4.7.0",
30
30
  "jose": "^6.1.2",
31
31
  "openai": "^6.7.0",
@@ -241,9 +241,10 @@ export class PuppeteerAdapter implements BrowserAdapter {
241
241
  type?: string;
242
242
  quality?: number;
243
243
  }): Promise<Uint8Array> {
244
+ const type = (options?.type as "jpeg" | "png") || "jpeg";
244
245
  const buffer = await this.page.screenshot({
245
- type: (options?.type as "jpeg" | "png") || "jpeg",
246
- quality: options?.quality || 72,
246
+ type,
247
+ ...(type === "jpeg" ? { quality: options?.quality || 72 } : {}),
247
248
  });
248
249
  return new Uint8Array(buffer);
249
250
  }
package/src/index.ts CHANGED
@@ -44,6 +44,7 @@ if (import.meta.main) {
44
44
  "runner-id": { type: "string" },
45
45
  "base-url": { type: "string" },
46
46
  "size-class": { type: "string", default: "desktop" },
47
+ "scan-mode": { type: "string" },
47
48
  },
48
49
  strict: false,
49
50
  });
@@ -54,6 +55,7 @@ if (import.meta.main) {
54
55
  const runnerId = Number(args["runner-id"]);
55
56
  const baseUrl = String(args["base-url"] ?? "");
56
57
  const sizeClass = String(args["size-class"] ?? "desktop");
58
+ const scanMode = readScanMode(args["scan-mode"]);
57
59
 
58
60
  logger.info({ scanId, runnerId }, "one-shot mode: executing test run");
59
61
  try {
@@ -63,6 +65,7 @@ if (import.meta.main) {
63
65
  scanUrl: baseUrl,
64
66
  baseUrl,
65
67
  sizeClass,
68
+ scanMode,
66
69
  runnerInstanceId: crypto.randomUUID(),
67
70
  runnerInstanceName: "mcp-runner",
68
71
  });
@@ -104,6 +107,14 @@ if (import.meta.main) {
104
107
  }
105
108
  }
106
109
 
110
+ function readScanMode(
111
+ value: unknown
112
+ ): "full" | "partial" | "minimum" | undefined {
113
+ return value === "full" || value === "partial" || value === "minimum"
114
+ ? value
115
+ : undefined;
116
+ }
117
+
107
118
  export default {
108
119
  port,
109
120
  fetch: app.fetch,
@@ -40,14 +40,12 @@ export interface RunOptions {
40
40
  loginUrl?: string;
41
41
  entityCredentialId?: number;
42
42
  quickScan?: boolean;
43
+ scanMode?: "full" | "partial" | "minimum";
43
44
  }
44
45
 
45
46
  export async function runFullScan(options: RunOptions): Promise<void> {
46
47
  const config = loadConfig();
47
- const api = getApiClient(
48
- config.apiUrl + "/api/v1/scanner",
49
- config.scannerApiKey
50
- );
48
+ const api = getApiClient(config.apiUrl, config.scannerApiKey);
51
49
  const { scanUrl, baseUrl, userEmail } = options;
52
50
  const runnerName = options.runnerName || new URL(scanUrl).hostname;
53
51
  const sizeClass = (options.sizeClass as SizeClass) || SizeClass.Desktop;
@@ -115,6 +113,7 @@ export async function runFullScan(options: RunOptions): Promise<void> {
115
113
  entityCredentialId: options.entityCredentialId,
116
114
  credentials: options.credentials,
117
115
  quickScan: options.quickScan,
116
+ scanMode: options.scanMode,
118
117
  },
119
118
  api,
120
119
  expertises,
@@ -138,7 +137,11 @@ export async function runFullScan(options: RunOptions): Promise<void> {
138
137
  });
139
138
  }
140
139
 
141
- await page.close();
140
+ try {
141
+ await page.close();
142
+ } catch (err) {
143
+ logger.debug({ err }, "page already closed during scan cleanup");
144
+ }
142
145
 
143
146
  logger.info(
144
147
  { scanId, sizeClass, totalDurationMs: elapsed(runStart) },
@@ -159,10 +162,7 @@ export async function runSequenceScan(
159
162
  options: SequenceRunOptions
160
163
  ): Promise<void> {
161
164
  const config = loadConfig();
162
- const api = getApiClient(
163
- config.apiUrl + "/api/v1/scanner",
164
- config.scannerApiKey
165
- );
165
+ const api = getApiClient(config.apiUrl, config.scannerApiKey);
166
166
 
167
167
  const runStart = Date.now();
168
168
  const sizeClass = (options.sizeClass as SizeClass) || SizeClass.Desktop;
@@ -205,7 +205,11 @@ export async function runSequenceScan(
205
205
  eventHandler
206
206
  );
207
207
 
208
- await page.close();
208
+ try {
209
+ await page.close();
210
+ } catch (err) {
211
+ logger.debug({ err }, "page already closed during sequence cleanup");
212
+ }
209
213
 
210
214
  logger.info(
211
215
  {
@@ -33,10 +33,7 @@ export class RunnerManager {
33
33
  this.tickInFlight = true;
34
34
  try {
35
35
  const config = loadConfig();
36
- const api = getApiClient(
37
- config.apiUrl + "/api/v1/scanner",
38
- config.scannerApiKey
39
- );
36
+ const api = getApiClient(config.apiUrl, config.scannerApiKey);
40
37
 
41
38
  while (this.activeRuns.size < this.maxConcurrentRunners) {
42
39
  const pendingRun = await api.getPendingTestRun();
@@ -57,23 +54,50 @@ export class RunnerManager {
57
54
  }
58
55
 
59
56
  const slotNumber = this.activeRuns.size + 1;
60
- const runner = await api.getRunner(pendingRun.runnerId);
57
+
58
+ let runner;
59
+ try {
60
+ runner = await api.getRunner(pendingRun.runnerId);
61
+ } catch (err) {
62
+ logger.error(
63
+ { err, runnerId: pendingRun.runnerId, runId: pendingRun.id },
64
+ "failed to fetch runner, will retry next tick"
65
+ );
66
+ break;
67
+ }
61
68
  if (!runner) {
62
69
  logger.error(
63
70
  { runnerId: pendingRun.runnerId, runId: pendingRun.id },
64
71
  "runner not found for pending run"
65
72
  );
66
- await api.completeTestRun(pendingRun.id, { status: "failed" });
73
+ try {
74
+ await api.completeTestRun(pendingRun.id, { status: "failed" });
75
+ } catch (err) {
76
+ logger.error(
77
+ { err, runId: pendingRun.id },
78
+ "failed to mark run as failed"
79
+ );
80
+ }
67
81
  continue;
68
82
  }
69
83
 
70
84
  const runnerInstanceId = `${this.processInstanceId}:${slotNumber}`;
71
85
  const runnerInstanceName = `${runner.title} [slot ${slotNumber}]`;
72
- const claimed = await api.claimTestRun(
73
- pendingRun.id,
74
- runnerInstanceId,
75
- runnerInstanceName
76
- );
86
+
87
+ let claimed;
88
+ try {
89
+ claimed = await api.claimTestRun(
90
+ pendingRun.id,
91
+ runnerInstanceId,
92
+ runnerInstanceName
93
+ );
94
+ } catch (err) {
95
+ logger.error(
96
+ { err, runId: pendingRun.id },
97
+ "failed to claim run, will retry next tick"
98
+ );
99
+ break;
100
+ }
77
101
  if (!claimed) {
78
102
  logger.info(
79
103
  { runId: pendingRun.id },
@@ -106,8 +130,11 @@ export class RunnerManager {
106
130
  runnerInstanceId,
107
131
  runnerInstanceName,
108
132
  quickScan: pendingRun.quickScan ?? false,
133
+ scanMode: readScanMode(pendingRun),
109
134
  });
110
135
  }
136
+ } catch (err) {
137
+ logger.error({ err }, "tick failed");
111
138
  } finally {
112
139
  this.tickInFlight = false;
113
140
  }
@@ -122,6 +149,7 @@ export class RunnerManager {
122
149
  runnerInstanceId: string;
123
150
  runnerInstanceName: string;
124
151
  quickScan: boolean;
152
+ scanMode?: "full" | "partial" | "minimum";
125
153
  }): Promise<void> {
126
154
  const config = loadConfig();
127
155
  const api = getApiClient(
@@ -140,6 +168,7 @@ export class RunnerManager {
140
168
  runnerInstanceId: params.runnerInstanceId,
141
169
  runnerInstanceName: params.runnerInstanceName,
142
170
  quickScan: params.quickScan,
171
+ scanMode: params.scanMode,
143
172
  });
144
173
 
145
174
  logger.info({ runId: params.runId }, "run completed successfully");
@@ -161,3 +190,12 @@ export class RunnerManager {
161
190
  }
162
191
  }
163
192
  }
193
+
194
+ function readScanMode(
195
+ run: unknown
196
+ ): "full" | "partial" | "minimum" | undefined {
197
+ const value = (run as { scanMode?: unknown }).scanMode;
198
+ return value === "full" || value === "partial" || value === "minimum"
199
+ ? value
200
+ : undefined;
201
+ }