@opice/harness 0.0.2 → 0.0.3

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/package.json +1 -1
  2. package/src/reporter.ts +42 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opice/harness",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Runtime primitives for opice — AI-driven E2E browser tests on top of agent-browser",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
package/src/reporter.ts CHANGED
@@ -81,6 +81,7 @@ export interface RunHandoff {
81
81
  class HttpReporter implements Reporter {
82
82
  private runIdPromise: Promise<string> | null = null
83
83
  private readonly pending: Set<Promise<unknown>> = new Set()
84
+ private warnedUnreachable = false
84
85
 
85
86
  constructor(private readonly config: ReporterConfig) {}
86
87
 
@@ -173,19 +174,51 @@ class HttpReporter implements Reporter {
173
174
  }
174
175
 
175
176
  private async fetch(method: string, path: string, body?: unknown): Promise<Record<string, unknown>> {
176
- const response = await fetch(this.config.endpoint + path, {
177
- method,
178
- headers: {
179
- 'authorization': `Bearer ${this.config.apiKey}`,
180
- 'content-type': 'application/json',
181
- },
182
- body: body == null ? undefined : JSON.stringify(body),
183
- })
177
+ let response: Response
178
+ try {
179
+ response = await fetch(this.config.endpoint + path, {
180
+ method,
181
+ headers: {
182
+ 'authorization': `Bearer ${this.config.apiKey}`,
183
+ 'content-type': 'application/json',
184
+ },
185
+ body: body == null ? undefined : JSON.stringify(body),
186
+ })
187
+ } catch (err) {
188
+ // Network error / blocked request (e.g. a test runner that installs a
189
+ // DOM and routes fetch through a same-origin policy). Callers swallow
190
+ // reporter errors so the test still runs, so this is the one place the
191
+ // failure is visible — make it loud and actionable.
192
+ this.warnUnreachable(`${method} ${path}`, err instanceof Error ? err.message : String(err))
193
+ throw err
194
+ }
184
195
  if (!response.ok) {
185
- throw new Error(`opice reporter ${method} ${path} failed: ${response.status} ${await response.text()}`)
196
+ const detail = `${response.status} ${await response.text()}`.trim()
197
+ this.warnUnreachable(`${method} ${path}`, detail)
198
+ throw new Error(`opice reporter ${method} ${path} failed: ${detail}`)
186
199
  }
187
200
  return (await response.json()) as Record<string, unknown>
188
201
  }
202
+
203
+ /**
204
+ * A configured reporter that can't reach the platform means the run is
205
+ * silently NOT recorded — the most confusing failure mode in onboarding
206
+ * (the test passes, but nothing shows on the dashboard). Surface it once,
207
+ * with the usual culprits, instead of letting the swallowed throw vanish.
208
+ */
209
+ private warnUnreachable(call: string, detail: string): void {
210
+ if (this.warnedUnreachable) return
211
+ this.warnedUnreachable = true
212
+ console.error(
213
+ `[opice] reporter could not reach the platform (${call}: ${detail}). `
214
+ + `This run will NOT be recorded on the dashboard.\n`
215
+ + `[opice] Common causes:\n`
216
+ + `[opice] - the test runner's global setup installs a DOM (happy-dom/jsdom) or mocks\n`
217
+ + `[opice] fetch, so the cross-origin POST is blocked (look for "Cross-Origin Request\n`
218
+ + `[opice] Blocked" / an OPTIONS … 401). Scope that setup so it skips the e2e dir.\n`
219
+ + `[opice] - a missing / expired OPICE_DSN api key (401), or an unreachable endpoint.`,
220
+ )
221
+ }
189
222
  }
190
223
 
191
224
  let active: Reporter = new NoopReporter()