@opice/harness 0.0.3 → 0.1.0
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/README.md +91 -27
- package/dist/accessible.d.ts +28 -0
- package/dist/accessible.d.ts.map +1 -0
- package/dist/accessible.js +31 -0
- package/dist/accessible.js.map +1 -0
- package/dist/command.d.ts +65 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/command.js +88 -0
- package/dist/command.js.map +1 -0
- package/dist/context.d.ts +10 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +50 -0
- package/dist/context.js.map +1 -0
- package/dist/dsn.d.ts +17 -0
- package/dist/dsn.d.ts.map +1 -0
- package/dist/dsn.js +17 -0
- package/dist/dsn.js.map +1 -0
- package/dist/element.d.ts +50 -0
- package/dist/element.d.ts.map +1 -0
- package/dist/element.js +82 -0
- package/dist/element.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/navigation.d.ts +22 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/navigation.js +35 -0
- package/dist/navigation.js.map +1 -0
- package/dist/reporter.d.ts +61 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +210 -0
- package/dist/reporter.js.map +1 -0
- package/dist/scenario.d.ts +30 -0
- package/dist/scenario.d.ts.map +1 -0
- package/dist/scenario.js +162 -0
- package/dist/scenario.js.map +1 -0
- package/package.json +11 -4
- package/src/accessible.ts +38 -0
- package/src/command.ts +134 -0
- package/src/context.ts +55 -0
- package/src/element.ts +56 -81
- package/src/index.ts +16 -1
- package/src/navigation.ts +41 -0
- package/src/reporter.ts +18 -0
- package/src/scenario.ts +38 -22
- package/src/agent-browser.ts +0 -30
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/reporter.js
ADDED
|
@@ -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"}
|
package/dist/scenario.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { closePage, launchPage } from './context.js';
|
|
4
|
+
import { screenshot } from './element.js';
|
|
5
|
+
import { getReporter } from './reporter.js';
|
|
6
|
+
/**
|
|
7
|
+
* `bun:test` is resolved lazily, at the moment `browserTest` registers a
|
|
8
|
+
* scenario — never at module load. That keeps `@opice/harness` importable
|
|
9
|
+
* under plain Node (the `opice-browser` authoring daemon imports the command
|
|
10
|
+
* registry from this package and runs on Node, where `bun:test` doesn't
|
|
11
|
+
* exist). Tests still register synchronously: `require` is sync under Bun.
|
|
12
|
+
*/
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
function bunTest() {
|
|
15
|
+
return require('bun:test');
|
|
16
|
+
}
|
|
17
|
+
const PLAYGROUND_URL = process.env['PLAYGROUND_URL'] ?? 'http://localhost:15180';
|
|
18
|
+
/**
|
|
19
|
+
* Best-effort capture of the `*.test.ts` path that called `browserTest`, by
|
|
20
|
+
* walking the stack for the first `.test.` frame. Reported so a failed
|
|
21
|
+
* scenario links back to its source file. Repo-relative when possible.
|
|
22
|
+
*/
|
|
23
|
+
function captureTestFile() {
|
|
24
|
+
const stack = new Error().stack;
|
|
25
|
+
if (!stack)
|
|
26
|
+
return undefined;
|
|
27
|
+
for (const line of stack.split('\n')) {
|
|
28
|
+
const match = line.match(/\(?((?:file:\/\/)?\/[^\s():]+\.test\.[tj]sx?)/);
|
|
29
|
+
if (match?.[1]) {
|
|
30
|
+
const abs = match[1].replace(/^file:\/\//, '');
|
|
31
|
+
try {
|
|
32
|
+
const rel = path.relative(process.cwd(), abs);
|
|
33
|
+
return rel.startsWith('..') ? abs : rel;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return abs;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
function defaultScenarioFile(testFile) {
|
|
43
|
+
if (!testFile)
|
|
44
|
+
return undefined;
|
|
45
|
+
return testFile.replace(/\.test\.[tj]sx?$/, '.scenario.md');
|
|
46
|
+
}
|
|
47
|
+
let currentScenarioId = null;
|
|
48
|
+
let currentScenarioStart = 0;
|
|
49
|
+
let currentScenarioFailures = 0;
|
|
50
|
+
// Monotonic per-scenario step counter. Assigned synchronously at each step()
|
|
51
|
+
// call so order reflects authoring order — step records are POSTed
|
|
52
|
+
// fire-and-forget and would otherwise be sequenced by arrival order at the
|
|
53
|
+
// worker, which screenshot-encoding latency can reshuffle.
|
|
54
|
+
let currentScenarioStepSeq = 0;
|
|
55
|
+
/**
|
|
56
|
+
* Register a top-level browser test scenario.
|
|
57
|
+
*
|
|
58
|
+
* Each `browserTest(name, fn)` launches its own isolated Playwright browser +
|
|
59
|
+
* context + page, navigates to the playground URL, runs the given `fn` (which
|
|
60
|
+
* typically contains nested `describe`/`test` blocks), and tears the browser
|
|
61
|
+
* down in `afterAll`.
|
|
62
|
+
*/
|
|
63
|
+
export function browserTest(name, fn, options = {}) {
|
|
64
|
+
const opts = typeof options === 'string' ? { hash: options } : options;
|
|
65
|
+
const reporter = getReporter();
|
|
66
|
+
const testFile = captureTestFile();
|
|
67
|
+
const scenarioFile = opts.scenarioFile ?? defaultScenarioFile(testFile);
|
|
68
|
+
const { describe, beforeAll, afterAll } = bunTest();
|
|
69
|
+
describe(name, () => {
|
|
70
|
+
beforeAll(async () => {
|
|
71
|
+
currentScenarioStart = Date.now();
|
|
72
|
+
currentScenarioFailures = 0;
|
|
73
|
+
currentScenarioStepSeq = 0;
|
|
74
|
+
try {
|
|
75
|
+
currentScenarioId = await reporter.startScenario({ name, hash: opts.hash, testFile, scenarioFile });
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
currentScenarioId = null;
|
|
79
|
+
}
|
|
80
|
+
const page = await launchPage();
|
|
81
|
+
const base = opts.url ?? PLAYGROUND_URL;
|
|
82
|
+
const url = opts.hash ? `${base}#${opts.hash}` : base;
|
|
83
|
+
await page.goto(url);
|
|
84
|
+
}, 30_000);
|
|
85
|
+
afterAll(async () => {
|
|
86
|
+
try {
|
|
87
|
+
await closePage();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// ignore close errors
|
|
91
|
+
}
|
|
92
|
+
if (currentScenarioId) {
|
|
93
|
+
// Drain pending step records (incl. their screenshot uploads)
|
|
94
|
+
// before marking the scenario done. step() fires recordStep
|
|
95
|
+
// fire-and-forget; the test process would otherwise exit while
|
|
96
|
+
// those requests were still in flight.
|
|
97
|
+
try {
|
|
98
|
+
await reporter.flush();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// best-effort
|
|
102
|
+
}
|
|
103
|
+
const durationMs = Date.now() - currentScenarioStart;
|
|
104
|
+
const status = currentScenarioFailures > 0 ? 'failed' : 'passed';
|
|
105
|
+
try {
|
|
106
|
+
await reporter.finishScenario({ scenarioId: currentScenarioId, status, durationMs });
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// best-effort
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
currentScenarioId = null;
|
|
113
|
+
}, 30_000);
|
|
114
|
+
fn();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* A reportable step inside a scenario. Captures duration + screenshot on
|
|
119
|
+
* finish, forwards to the active reporter (no-op unless configured via env).
|
|
120
|
+
*
|
|
121
|
+
* The body may be sync or async; `step` always returns a promise, so call it
|
|
122
|
+
* with `await step('…', async () => { … })`.
|
|
123
|
+
*/
|
|
124
|
+
export async function step(name, fn) {
|
|
125
|
+
const reporter = getReporter();
|
|
126
|
+
// Capture order at call time, before the fire-and-forget record below.
|
|
127
|
+
const sequence = currentScenarioStepSeq++;
|
|
128
|
+
const start = Date.now();
|
|
129
|
+
let status = 'passed';
|
|
130
|
+
let error;
|
|
131
|
+
try {
|
|
132
|
+
await fn();
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
status = 'failed';
|
|
136
|
+
error = e instanceof Error ? e.message : String(e);
|
|
137
|
+
currentScenarioFailures++;
|
|
138
|
+
throw e;
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
const durationMs = Date.now() - start;
|
|
142
|
+
let screenshotPath;
|
|
143
|
+
try {
|
|
144
|
+
screenshotPath = await screenshot();
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// screenshot failure shouldn't fail the test
|
|
148
|
+
}
|
|
149
|
+
if (currentScenarioId) {
|
|
150
|
+
void reporter.recordStep({
|
|
151
|
+
scenarioId: currentScenarioId,
|
|
152
|
+
sequence,
|
|
153
|
+
name,
|
|
154
|
+
status,
|
|
155
|
+
durationMs,
|
|
156
|
+
error,
|
|
157
|
+
screenshotPath,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=scenario.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario.js","sourceRoot":"","sources":["../src/scenario.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opice/harness",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "Runtime primitives for opice — AI-driven E2E browser tests on top of
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Runtime primitives for opice — AI-driven E2E browser tests on top of Playwright",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
7
|
"types": "./src/index.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./src/index.ts",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
11
|
+
"bun": "./src/index.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
13
14
|
}
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
16
17
|
"src",
|
|
18
|
+
"dist",
|
|
17
19
|
"README.md"
|
|
18
20
|
],
|
|
19
21
|
"scripts": {
|
|
@@ -31,5 +33,10 @@
|
|
|
31
33
|
},
|
|
32
34
|
"publishConfig": {
|
|
33
35
|
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@playwright/test": "^1.60.0",
|
|
39
|
+
"playwright": "^1.60.0",
|
|
40
|
+
"zod": "^4.4.3"
|
|
34
41
|
}
|
|
35
42
|
}
|