@opice/harness 0.0.4 → 0.1.1

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 (46) hide show
  1. package/README.md +80 -44
  2. package/dist/accessible.d.ts +28 -0
  3. package/dist/accessible.d.ts.map +1 -0
  4. package/dist/accessible.js +31 -0
  5. package/dist/accessible.js.map +1 -0
  6. package/dist/command.d.ts +65 -0
  7. package/dist/command.d.ts.map +1 -0
  8. package/dist/command.js +88 -0
  9. package/dist/command.js.map +1 -0
  10. package/dist/context.d.ts +10 -0
  11. package/dist/context.d.ts.map +1 -0
  12. package/dist/context.js +50 -0
  13. package/dist/context.js.map +1 -0
  14. package/dist/dsn.d.ts +17 -0
  15. package/dist/dsn.d.ts.map +1 -0
  16. package/dist/dsn.js +17 -0
  17. package/dist/dsn.js.map +1 -0
  18. package/dist/element.d.ts +50 -0
  19. package/dist/element.d.ts.map +1 -0
  20. package/dist/element.js +82 -0
  21. package/dist/element.js.map +1 -0
  22. package/dist/index.d.ts +15 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +12 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/navigation.d.ts +22 -0
  27. package/dist/navigation.d.ts.map +1 -0
  28. package/dist/navigation.js +35 -0
  29. package/dist/navigation.js.map +1 -0
  30. package/dist/reporter.d.ts +61 -0
  31. package/dist/reporter.d.ts.map +1 -0
  32. package/dist/reporter.js +210 -0
  33. package/dist/reporter.js.map +1 -0
  34. package/dist/scenario.d.ts +30 -0
  35. package/dist/scenario.d.ts.map +1 -0
  36. package/dist/scenario.js +162 -0
  37. package/dist/scenario.js.map +1 -0
  38. package/package.json +11 -4
  39. package/src/accessible.ts +21 -187
  40. package/src/command.ts +134 -0
  41. package/src/context.ts +55 -0
  42. package/src/element.ts +56 -99
  43. package/src/index.ts +12 -1
  44. package/src/navigation.ts +16 -28
  45. package/src/scenario.ts +29 -22
  46. package/src/agent-browser.ts +0 -30
@@ -0,0 +1,82 @@
1
+ import { getPage } from './context.js';
2
+ const POLL_INTERVAL = 200;
3
+ const POLL_TIMEOUT = 10_000;
4
+ /**
5
+ * Resolve a selector into a `Locator` on an explicit page — the shared core
6
+ * behind `el()` and the command-registry context. Bare identifiers become
7
+ * test-ids (`getByTestId`, matching `data-testid`); anything with CSS-flavoured
8
+ * characters (`[ ] . # : > ` or a space) is a raw CSS selector.
9
+ */
10
+ export function locatorOn(page, selectorOrTestId) {
11
+ if (/[\[\].#:> ]/.test(selectorOrTestId)) {
12
+ return page.locator(selectorOrTestId);
13
+ }
14
+ return page.getByTestId(selectorOrTestId);
15
+ }
16
+ /**
17
+ * Resolve a selector into a Playwright `Locator`.
18
+ *
19
+ * Bare identifiers are auto-wrapped as test-ids (`page.getByTestId`, which
20
+ * matches `data-testid` by default); anything with CSS-flavoured characters
21
+ * (`[ ] . # : > ` or a space) is treated as a raw CSS selector. Heuristic — if
22
+ * you need a plain-tag selector (e.g. `h1`), give it structure (`main h1`).
23
+ *
24
+ * The returned value is a real Playwright `Locator`, so the full Locator API
25
+ * (`.click()`, `.fill()`, `.textContent()`, `.first()`, `.nth()`, …) and the
26
+ * web-first `expect(locator)` assertions are available. All actions auto-wait
27
+ * for actionability and fire real user gestures.
28
+ */
29
+ export function el(selectorOrTestId) {
30
+ return locatorOn(getPage(), selectorOrTestId);
31
+ }
32
+ /**
33
+ * Build a `[data-testid="..."]` selector string, for composing into a larger
34
+ * CSS selector: `el(`${tid('row')} button`)`. For a plain test-id, prefer
35
+ * `el('row')` directly.
36
+ */
37
+ export function tid(testId) {
38
+ return `[data-testid="${testId}"]`;
39
+ }
40
+ /**
41
+ * Poll a (possibly async) condition until it returns true or times out.
42
+ *
43
+ * Prefer Playwright's retrying assertions — `await expect(el('x')).toBeVisible()`,
44
+ * `.toHaveText(...)` — which auto-wait and give better failure messages. Keep
45
+ * `waitFor` for arbitrary predicates that don't map to a locator assertion.
46
+ */
47
+ export async function waitFor(condition, { timeout = POLL_TIMEOUT, interval = POLL_INTERVAL, message } = {}) {
48
+ const start = Date.now();
49
+ while (Date.now() - start < timeout) {
50
+ try {
51
+ if (await condition())
52
+ return;
53
+ }
54
+ catch {
55
+ // condition threw — treat as not yet ready
56
+ }
57
+ await new Promise((resolve) => setTimeout(resolve, interval));
58
+ }
59
+ if (!(await condition())) {
60
+ const elapsed = Date.now() - start;
61
+ const hint = message ?? condition.toString().slice(0, 120);
62
+ throw new Error(`waitFor timed out after ${elapsed}ms: ${hint}`);
63
+ }
64
+ }
65
+ /** Fixed sleep. Avoid when possible — prefer `waitFor` or retrying assertions. */
66
+ export async function wait(ms) {
67
+ await new Promise((resolve) => setTimeout(resolve, ms));
68
+ }
69
+ /**
70
+ * Evaluate JavaScript in the page and return its result. Thin wrapper over
71
+ * `page.evaluate`; the value is the real JS value (not a JSON string).
72
+ */
73
+ export function evalJs(js) {
74
+ return getPage().evaluate(js);
75
+ }
76
+ /** Capture a screenshot to `path` (or a temp file) and return the path. */
77
+ export async function screenshot(path) {
78
+ const target = path ?? `/tmp/opice-screenshot-${Date.now()}.png`;
79
+ await getPage().screenshot({ path: target });
80
+ return target;
81
+ }
82
+ //# sourceMappingURL=element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.js","sourceRoot":"","sources":["../src/element.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,MAAM,aAAa,GAAG,GAAG,CAAA;AACzB,MAAM,YAAY,GAAG,MAAM,CAAA;AAE3B;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,IAAU,EAAE,gBAAwB;IAC7D,IAAI,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAA;IACtC,CAAC;IACD,OAAO,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAA;AAC1C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,EAAE,CAAC,gBAAwB;IAC1C,OAAO,SAAS,CAAC,OAAO,EAAE,EAAE,gBAAgB,CAAC,CAAA;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,GAAG,CAAC,MAAc;IACjC,OAAO,iBAAiB,MAAM,IAAI,CAAA;AACnC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC5B,SAA2C,EAC3C,EAAE,OAAO,GAAG,YAAY,EAAE,QAAQ,GAAG,aAAa,EAAE,OAAO,KAAgE,EAAE;IAE7H,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACxB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,OAAO,EAAE,CAAC;QACrC,IAAI,CAAC;YACJ,IAAI,MAAM,SAAS,EAAE;gBAAE,OAAM;QAC9B,CAAC;QAAC,MAAM,CAAC;YACR,2CAA2C;QAC5C,CAAC;QACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;IAC9D,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,SAAS,EAAE,CAAC,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAA;QAClC,MAAM,IAAI,GAAG,OAAO,IAAI,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;QAC1D,MAAM,IAAI,KAAK,CAAC,2BAA2B,OAAO,OAAO,IAAI,EAAE,CAAC,CAAA;IACjE,CAAC;AACF,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,EAAU;IACpC,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AACxD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAc,EAAU;IAC7C,OAAO,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAe,CAAA;AAC5C,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAa;IAC7C,MAAM,MAAM,GAAG,IAAI,IAAI,yBAAyB,IAAI,CAAC,GAAG,EAAE,MAAM,CAAA;IAChE,MAAM,OAAO,EAAE,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;IAC5C,OAAO,MAAM,CAAA;AACd,CAAC"}
@@ -0,0 +1,15 @@
1
+ export { el, tid, waitFor, wait, evalJs, screenshot } from './element.js';
2
+ export { byLabel, byRole, byText } from './accessible.js';
3
+ export { back, currentPath, currentUrl, forward, open, reload } from './navigation.js';
4
+ export { getPage, getContext } from './context.js';
5
+ export { browserTest, step } from './scenario.js';
6
+ export type { BrowserTestOptions } from './scenario.js';
7
+ export { getReporter, setReporter, configureFromEnv } from './reporter.js';
8
+ export type { Reporter, ReporterConfig, StepEvent, ScenarioStart, ScenarioFinish } from './reporter.js';
9
+ export { parseOpiceDsn } from './dsn.js';
10
+ export type { OpiceDsn } from './dsn.js';
11
+ export { command, call, runCommand, makeCtx, loadUserCommands, findUserCommandsFile, z } from './command.js';
12
+ export type { Command, CommandCtx } from './command.js';
13
+ export { expect } from '@playwright/test';
14
+ export type { Locator } from 'playwright';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAEzD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAEtF,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AACjD,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAEvD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAC1E,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEvG,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAC5G,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAIvD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAGzC,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export { el, tid, waitFor, wait, evalJs, screenshot } from './element.js';
2
+ export { byLabel, byRole, byText } from './accessible.js';
3
+ export { back, currentPath, currentUrl, forward, open, reload } from './navigation.js';
4
+ export { getPage, getContext } from './context.js';
5
+ export { browserTest, step } from './scenario.js';
6
+ export { getReporter, setReporter, configureFromEnv } from './reporter.js';
7
+ export { parseOpiceDsn } from './dsn.js';
8
+ export { command, call, runCommand, makeCtx, loadUserCommands, findUserCommandsFile, z } from './command.js';
9
+ // Playwright's web-first `expect` (retrying locator matchers + generic matchers)
10
+ // works under `bun:test`; re-export it so tests use a single `expect`.
11
+ export { expect } from '@playwright/test';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAEzD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAEtF,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAGjD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAG1E,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAGxC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAG5G,iFAAiF;AACjF,uEAAuE;AACvE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Page navigation primitives. `browserTest` opens the scenario URL for you in
3
+ * `beforeAll`; these are for mid-scenario navigation — following a hard link,
4
+ * reloading after mutating storage/cookies, or going back/forward.
5
+ *
6
+ * Each navigating call waits for the `load` event (Playwright's default), so
7
+ * the old agent-browser reload caveat (a reload from inside `eval` getting
8
+ * dropped) no longer applies — `reload()` drives the page directly.
9
+ */
10
+ /** Navigate to a URL in the current page. */
11
+ export declare function open(url: string): Promise<void>;
12
+ /** Reload the current page. */
13
+ export declare function reload(): Promise<void>;
14
+ /** Go back in history. */
15
+ export declare function back(): Promise<void>;
16
+ /** Go forward in history. */
17
+ export declare function forward(): Promise<void>;
18
+ /** The current full URL (`location.href`). */
19
+ export declare function currentUrl(): string;
20
+ /** The current path (`location.pathname`). */
21
+ export declare function currentPath(): string;
22
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,6CAA6C;AAC7C,wBAAsB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAErD;AAED,+BAA+B;AAC/B,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAE5C;AAED,0BAA0B;AAC1B,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAE1C;AAED,6BAA6B;AAC7B,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAE7C;AAED,8CAA8C;AAC9C,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,8CAA8C;AAC9C,wBAAgB,WAAW,IAAI,MAAM,CAEpC"}
@@ -0,0 +1,35 @@
1
+ import { getPage } from './context.js';
2
+ /**
3
+ * Page navigation primitives. `browserTest` opens the scenario URL for you in
4
+ * `beforeAll`; these are for mid-scenario navigation — following a hard link,
5
+ * reloading after mutating storage/cookies, or going back/forward.
6
+ *
7
+ * Each navigating call waits for the `load` event (Playwright's default), so
8
+ * the old agent-browser reload caveat (a reload from inside `eval` getting
9
+ * dropped) no longer applies — `reload()` drives the page directly.
10
+ */
11
+ /** Navigate to a URL in the current page. */
12
+ export async function open(url) {
13
+ await getPage().goto(url);
14
+ }
15
+ /** Reload the current page. */
16
+ export async function reload() {
17
+ await getPage().reload();
18
+ }
19
+ /** Go back in history. */
20
+ export async function back() {
21
+ await getPage().goBack();
22
+ }
23
+ /** Go forward in history. */
24
+ export async function forward() {
25
+ await getPage().goForward();
26
+ }
27
+ /** The current full URL (`location.href`). */
28
+ export function currentUrl() {
29
+ return getPage().url();
30
+ }
31
+ /** The current path (`location.pathname`). */
32
+ export function currentPath() {
33
+ return new URL(getPage().url()).pathname;
34
+ }
35
+ //# sourceMappingURL=navigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.js","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC;;;;;;;;GAQG;AAEH,6CAA6C;AAC7C,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,GAAW;IACrC,MAAM,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC1B,CAAC;AAED,+BAA+B;AAC/B,MAAM,CAAC,KAAK,UAAU,MAAM;IAC3B,MAAM,OAAO,EAAE,CAAC,MAAM,EAAE,CAAA;AACzB,CAAC;AAED,0BAA0B;AAC1B,MAAM,CAAC,KAAK,UAAU,IAAI;IACzB,MAAM,OAAO,EAAE,CAAC,MAAM,EAAE,CAAA;AACzB,CAAC;AAED,6BAA6B;AAC7B,MAAM,CAAC,KAAK,UAAU,OAAO;IAC5B,MAAM,OAAO,EAAE,CAAC,SAAS,EAAE,CAAA;AAC5B,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,UAAU;IACzB,OAAO,OAAO,EAAE,CAAC,GAAG,EAAE,CAAA;AACvB,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,WAAW;IAC1B,OAAO,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAA;AACzC,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Reporter — streams scenario/step/screenshot events to the opice platform.
3
+ *
4
+ * Steps are fire-and-forget (tracked in a pending queue so flush awaits
5
+ * them). Scenario create + finish are awaited inline so the platform sees
6
+ * the right status when the test process exits.
7
+ *
8
+ * The CLI handles end-of-run finalization: the reporter writes a
9
+ * handoff file under $TMPDIR with the runId and credentials, the
10
+ * `opice test` wrapper picks it up after `bun test` exits and POSTs
11
+ * /api/v1/runs/<id>/finish so the dashboard sees the run as completed.
12
+ *
13
+ * When env vars aren't configured, the reporter falls back to a no-op so
14
+ * harness behavior matches the bindx prototype.
15
+ */
16
+ export interface ReporterConfig {
17
+ endpoint: string;
18
+ projectId: string;
19
+ apiKey: string;
20
+ branch?: string;
21
+ commit?: string;
22
+ /** 'ci' for runs from automation, 'local' for opted-in dev runs. */
23
+ source?: 'ci' | 'local';
24
+ }
25
+ export interface StepEvent {
26
+ scenarioId: string;
27
+ /** Authoring order within the scenario, assigned at step() call time. */
28
+ sequence: number;
29
+ name: string;
30
+ status: 'passed' | 'failed';
31
+ durationMs: number;
32
+ error?: string;
33
+ screenshotPath?: string;
34
+ }
35
+ export interface ScenarioStart {
36
+ name: string;
37
+ hash?: string;
38
+ testFile?: string;
39
+ scenarioFile?: string;
40
+ }
41
+ export interface ScenarioFinish {
42
+ scenarioId: string;
43
+ status: 'passed' | 'failed';
44
+ durationMs: number;
45
+ }
46
+ export interface Reporter {
47
+ startScenario(input: ScenarioStart): Promise<string>;
48
+ recordStep(event: StepEvent): Promise<void>;
49
+ finishScenario(input: ScenarioFinish): Promise<void>;
50
+ flush(): Promise<void>;
51
+ }
52
+ export declare const HANDOFF_DIR: string;
53
+ export interface RunHandoff {
54
+ endpoint: string;
55
+ apiKey: string;
56
+ runId: string;
57
+ }
58
+ export declare function getReporter(): Reporter;
59
+ export declare function setReporter(reporter: Reporter): void;
60
+ export declare function configureFromEnv(env?: NodeJS.ProcessEnv): Reporter;
61
+ //# sourceMappingURL=reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAQH,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oEAAoE;IACpE,MAAM,CAAC,EAAE,IAAI,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,SAAS;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC3B,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,cAAc,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC3B,UAAU,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,QAAQ;IACxB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACpD,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3C,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB;AAWD,eAAO,MAAM,WAAW,QAAwC,CAAA;AAMhE,MAAM,WAAW,UAAU;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;CACb;AAqJD,wBAAgB,WAAW,IAAI,QAAQ,CAEtC;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAEpD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,QAAQ,CA8B/E"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Reporter — streams scenario/step/screenshot events to the opice platform.
3
+ *
4
+ * Steps are fire-and-forget (tracked in a pending queue so flush awaits
5
+ * them). Scenario create + finish are awaited inline so the platform sees
6
+ * the right status when the test process exits.
7
+ *
8
+ * The CLI handles end-of-run finalization: the reporter writes a
9
+ * handoff file under $TMPDIR with the runId and credentials, the
10
+ * `opice test` wrapper picks it up after `bun test` exits and POSTs
11
+ * /api/v1/runs/<id>/finish so the dashboard sees the run as completed.
12
+ *
13
+ * When env vars aren't configured, the reporter falls back to a no-op so
14
+ * harness behavior matches the bindx prototype.
15
+ */
16
+ import { promises as fs } from 'node:fs';
17
+ import { mkdirSync, writeFileSync } from 'node:fs';
18
+ import { tmpdir } from 'node:os';
19
+ import path from 'node:path';
20
+ import { parseOpiceDsn } from './dsn.js';
21
+ class NoopReporter {
22
+ async startScenario(input) {
23
+ return `noop-${input.name}-${Date.now()}`;
24
+ }
25
+ async recordStep(_event) { }
26
+ async finishScenario(_input) { }
27
+ async flush() { }
28
+ }
29
+ export const HANDOFF_DIR = path.join(tmpdir(), 'opice-handoffs');
30
+ function handoffPath(pid = process.pid) {
31
+ return path.join(HANDOFF_DIR, `${pid}.json`);
32
+ }
33
+ class HttpReporter {
34
+ config;
35
+ runIdPromise = null;
36
+ pending = new Set();
37
+ warnedUnreachable = false;
38
+ constructor(config) {
39
+ this.config = config;
40
+ }
41
+ async ensureRun() {
42
+ if (!this.runIdPromise) {
43
+ this.runIdPromise = this.startRun();
44
+ }
45
+ return this.runIdPromise;
46
+ }
47
+ async startRun() {
48
+ const response = await this.fetch('POST', '/api/v1/runs', {
49
+ branch: this.config.branch,
50
+ commit: this.config.commit,
51
+ source: this.config.source,
52
+ });
53
+ const runId = response['runId'];
54
+ // Synchronous write so the CLI can pick this up even if the test
55
+ // process exits abruptly (process.on('exit') runs sync).
56
+ try {
57
+ mkdirSync(HANDOFF_DIR, { recursive: true });
58
+ const handoff = { endpoint: this.config.endpoint, apiKey: this.config.apiKey, runId };
59
+ writeFileSync(handoffPath(), JSON.stringify(handoff), 'utf-8');
60
+ }
61
+ catch {
62
+ // best-effort
63
+ }
64
+ return runId;
65
+ }
66
+ async startScenario(input) {
67
+ const runId = await this.ensureRun();
68
+ const response = await this.fetch('POST', `/api/v1/runs/${runId}/scenarios`, {
69
+ name: input.name,
70
+ hash: input.hash,
71
+ testFile: input.testFile,
72
+ scenarioFile: input.scenarioFile,
73
+ });
74
+ return response['scenarioId'];
75
+ }
76
+ recordStep(event) {
77
+ // Track synchronously so flush() awaits the entire pipeline (including
78
+ // encodeScreenshot's fs.readFile and the upload), not just whatever
79
+ // fragment has run by the time afterAll fires.
80
+ const promise = this.recordStepInternal(event);
81
+ this.track(promise);
82
+ return promise;
83
+ }
84
+ async recordStepInternal(event) {
85
+ const runId = await this.ensureRun();
86
+ const screenshot = event.screenshotPath
87
+ ? await this.encodeScreenshot(event.screenshotPath)
88
+ : undefined;
89
+ await this.fetch('POST', `/api/v1/runs/${runId}/scenarios/${event.scenarioId}/steps`, {
90
+ sequence: event.sequence,
91
+ name: event.name,
92
+ status: event.status,
93
+ durationMs: event.durationMs,
94
+ error: event.error,
95
+ screenshot,
96
+ });
97
+ }
98
+ async finishScenario(input) {
99
+ const runId = await this.ensureRun();
100
+ // Awaited inline so the scenario status is committed before the
101
+ // bun:test afterAll returns.
102
+ await this.fetch('PATCH', `/api/v1/runs/${runId}/scenarios/${input.scenarioId}`, {
103
+ status: input.status,
104
+ durationMs: input.durationMs,
105
+ });
106
+ }
107
+ async flush() {
108
+ await Promise.allSettled([...this.pending]);
109
+ // finishRun is the CLI's responsibility — see handoff file.
110
+ }
111
+ track(promise) {
112
+ this.pending.add(promise);
113
+ promise.finally(() => this.pending.delete(promise));
114
+ }
115
+ async encodeScreenshot(path) {
116
+ try {
117
+ const buf = await fs.readFile(path);
118
+ return buf.toString('base64');
119
+ }
120
+ catch {
121
+ return undefined;
122
+ }
123
+ }
124
+ async fetch(method, path, body) {
125
+ let response;
126
+ try {
127
+ response = await fetch(this.config.endpoint + path, {
128
+ method,
129
+ headers: {
130
+ 'authorization': `Bearer ${this.config.apiKey}`,
131
+ 'content-type': 'application/json',
132
+ },
133
+ body: body == null ? undefined : JSON.stringify(body),
134
+ });
135
+ }
136
+ catch (err) {
137
+ // Network error / blocked request (e.g. a test runner that installs a
138
+ // DOM and routes fetch through a same-origin policy). Callers swallow
139
+ // reporter errors so the test still runs, so this is the one place the
140
+ // failure is visible — make it loud and actionable.
141
+ this.warnUnreachable(`${method} ${path}`, err instanceof Error ? err.message : String(err));
142
+ throw err;
143
+ }
144
+ if (!response.ok) {
145
+ const detail = `${response.status} ${await response.text()}`.trim();
146
+ this.warnUnreachable(`${method} ${path}`, detail);
147
+ throw new Error(`opice reporter ${method} ${path} failed: ${detail}`);
148
+ }
149
+ return (await response.json());
150
+ }
151
+ /**
152
+ * A configured reporter that can't reach the platform means the run is
153
+ * silently NOT recorded — the most confusing failure mode in onboarding
154
+ * (the test passes, but nothing shows on the dashboard). Surface it once,
155
+ * with the usual culprits, instead of letting the swallowed throw vanish.
156
+ */
157
+ warnUnreachable(call, detail) {
158
+ if (this.warnedUnreachable)
159
+ return;
160
+ this.warnedUnreachable = true;
161
+ console.error(`[opice] reporter could not reach the platform (${call}: ${detail}). `
162
+ + `This run will NOT be recorded on the dashboard.\n`
163
+ + `[opice] Common causes:\n`
164
+ + `[opice] - the test runner's global setup installs a DOM (happy-dom/jsdom) or mocks\n`
165
+ + `[opice] fetch, so the cross-origin POST is blocked (look for "Cross-Origin Request\n`
166
+ + `[opice] Blocked" / an OPTIONS … 401). Scope that setup so it skips the e2e dir.\n`
167
+ + `[opice] - a missing / expired OPICE_DSN api key (401), or an unreachable endpoint.`);
168
+ }
169
+ }
170
+ let active = new NoopReporter();
171
+ export function getReporter() {
172
+ return active;
173
+ }
174
+ export function setReporter(reporter) {
175
+ active = reporter;
176
+ }
177
+ export function configureFromEnv(env = process.env) {
178
+ // Individual vars win; OPICE_DSN fills any gaps (see dsn.ts).
179
+ const dsn = parseOpiceDsn(env['OPICE_DSN']);
180
+ const endpoint = env['OPICE_ENDPOINT'] ?? dsn?.endpoint;
181
+ const projectId = env['OPICE_PROJECT'] ?? dsn?.project;
182
+ const apiKey = env['OPICE_API_KEY'] ?? dsn?.apiKey;
183
+ if (!endpoint || !projectId || !apiKey) {
184
+ return new NoopReporter();
185
+ }
186
+ // Reporting is opt-in outside CI. A local `bun test` while authoring would
187
+ // otherwise stream half-finished runs onto the shared dashboard (they never
188
+ // get the CLI's POST /finish, so they'd sit there as "running" forever).
189
+ // CI reports automatically; OPICE_REPORT=always forces it locally, =never
190
+ // silences it everywhere.
191
+ const isCI = !!(env['CI'] || env['GITHUB_ACTIONS']);
192
+ const mode = (env['OPICE_REPORT'] ?? 'auto').toLowerCase();
193
+ const shouldReport = mode === 'never' ? false : mode === 'always' ? true : isCI;
194
+ if (!shouldReport) {
195
+ return new NoopReporter();
196
+ }
197
+ const reporter = new HttpReporter({
198
+ endpoint,
199
+ projectId,
200
+ apiKey,
201
+ branch: env['OPICE_BRANCH'] ?? env['GITHUB_REF_NAME'],
202
+ commit: env['OPICE_COMMIT'] ?? env['GITHUB_SHA'],
203
+ source: isCI ? 'ci' : 'local',
204
+ });
205
+ setReporter(reporter);
206
+ return reporter;
207
+ }
208
+ // Auto-configure when imported.
209
+ configureFromEnv();
210
+ //# sourceMappingURL=reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ export interface BrowserTestOptions {
2
+ /** Hash fragment appended to PLAYGROUND_URL (e.g. 'datagrid'). */
3
+ hash?: string;
4
+ /** Override base URL (defaults to PLAYGROUND_URL env). */
5
+ url?: string;
6
+ /**
7
+ * Path to the human-readable `*.scenario.md` this test was authored from.
8
+ * Reported to the platform so the re-eval workflow can find the source.
9
+ * If omitted, defaults to the test file path with `.test.ts` → `.scenario.md`.
10
+ */
11
+ scenarioFile?: string;
12
+ }
13
+ /**
14
+ * Register a top-level browser test scenario.
15
+ *
16
+ * Each `browserTest(name, fn)` launches its own isolated Playwright browser +
17
+ * context + page, navigates to the playground URL, runs the given `fn` (which
18
+ * typically contains nested `describe`/`test` blocks), and tears the browser
19
+ * down in `afterAll`.
20
+ */
21
+ export declare function browserTest(name: string, fn: () => void, options?: BrowserTestOptions | string): void;
22
+ /**
23
+ * A reportable step inside a scenario. Captures duration + screenshot on
24
+ * finish, forwards to the active reporter (no-op unless configured via env).
25
+ *
26
+ * The body may be sync or async; `step` always returns a promise, so call it
27
+ * with `await step('…', async () => { … })`.
28
+ */
29
+ export declare function step(name: string, fn: () => void | Promise<void>): Promise<void>;
30
+ //# sourceMappingURL=scenario.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenario.d.ts","sourceRoot":"","sources":["../src/scenario.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,kBAAkB;IAClC,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACrB;AAuCD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,EAAE,OAAO,GAAE,kBAAkB,GAAG,MAAW,GAAG,IAAI,CAoDzG;AAED;;;;;;GAMG;AACH,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAkCtF"}