@opice/harness 0.1.1 → 0.2.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.
- package/README.md +22 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/navigation.d.ts +0 -9
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +8 -4
- package/dist/navigation.js.map +1 -1
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +14 -1
- package/dist/reporter.js.map +1 -1
- package/dist/scenario.d.ts.map +1 -1
- package/dist/scenario.js +13 -2
- package/dist/scenario.js.map +1 -1
- package/dist/setup.d.ts +32 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +35 -0
- package/dist/setup.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/navigation.ts +9 -4
- package/src/reporter.ts +15 -1
- package/src/scenario.ts +12 -2
- package/src/setup.ts +54 -0
package/README.md
CHANGED
|
@@ -111,6 +111,28 @@ import { fullEnum } from '../browser-tools'
|
|
|
111
111
|
await call(fullEnum, { label: 'Typ', option: 'Faktura' })
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
### Context setup (user-land)
|
|
115
|
+
|
|
116
|
+
Export `setup(context)` from `<repo>/browser-setup.ts` to configure the browser
|
|
117
|
+
**context** once, **before the first navigation** — on both faces (the test
|
|
118
|
+
harness runs it in `beforeAll` before `page.goto`; the `opice-browser` server
|
|
119
|
+
runs it after connecting, before navigating to the launch URL). Because it runs
|
|
120
|
+
pre-navigation, an `addInitScript` here fires before the app's own scripts on
|
|
121
|
+
first paint — the place to seed storage/cookies, grant permissions, or set a
|
|
122
|
+
boot-time flag (e.g. "automated run — skip dev-only chrome"). Keep it
|
|
123
|
+
idempotent.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
// browser-setup.ts
|
|
127
|
+
import type { BrowserSetup } from '@opice/harness'
|
|
128
|
+
|
|
129
|
+
export const setup: BrowserSetup = async (context) => {
|
|
130
|
+
await context.addInitScript(() => {
|
|
131
|
+
try { localStorage.setItem('app:e2e', '1') } catch {}
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
114
136
|
### Misc
|
|
115
137
|
|
|
116
138
|
- `screenshot(path?)` — saves a PNG, returns the path (default under `/tmp/`).
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export { parseOpiceDsn } from './dsn.js';
|
|
|
10
10
|
export type { OpiceDsn } from './dsn.js';
|
|
11
11
|
export { command, call, runCommand, makeCtx, loadUserCommands, findUserCommandsFile, z } from './command.js';
|
|
12
12
|
export type { Command, CommandCtx } from './command.js';
|
|
13
|
+
export { loadUserSetup, findUserSetupFile } from './setup.js';
|
|
14
|
+
export type { BrowserSetup } from './setup.js';
|
|
13
15
|
export { expect } from '@playwright/test';
|
|
14
16
|
export type { Locator } from 'playwright';
|
|
15
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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;AAEvD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC7D,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAI9C,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAGzC,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { browserTest, step } from './scenario.js';
|
|
|
6
6
|
export { getReporter, setReporter, configureFromEnv } from './reporter.js';
|
|
7
7
|
export { parseOpiceDsn } from './dsn.js';
|
|
8
8
|
export { command, call, runCommand, makeCtx, loadUserCommands, findUserCommandsFile, z } from './command.js';
|
|
9
|
+
export { loadUserSetup, findUserSetupFile } from './setup.js';
|
|
9
10
|
// Playwright's web-first `expect` (retrying locator matchers + generic matchers)
|
|
10
11
|
// works under `bun:test`; re-export it so tests use a single `expect`.
|
|
11
12
|
export { expect } from '@playwright/test';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"}
|
|
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,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAG7D,iFAAiF;AACjF,uEAAuE;AACvE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA"}
|
package/dist/navigation.d.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
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
1
|
/** Navigate to a URL in the current page. */
|
|
11
2
|
export declare function open(url: string): Promise<void>;
|
|
12
3
|
/** Reload the current page. */
|
package/dist/navigation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAiBA,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"}
|
package/dist/navigation.js
CHANGED
|
@@ -8,21 +8,25 @@ import { getPage } from './context.js';
|
|
|
8
8
|
* the old agent-browser reload caveat (a reload from inside `eval` getting
|
|
9
9
|
* dropped) no longer applies — `reload()` drives the page directly.
|
|
10
10
|
*/
|
|
11
|
+
// SPA pages can hold the `load` event on a slow chunk or a long-lived
|
|
12
|
+
// connection, so every navigation waits for `domcontentloaded` (not the default
|
|
13
|
+
// `load`) and lets the test's retrying assertions handle readiness.
|
|
14
|
+
const WAIT_UNTIL = { waitUntil: 'domcontentloaded' };
|
|
11
15
|
/** Navigate to a URL in the current page. */
|
|
12
16
|
export async function open(url) {
|
|
13
|
-
await getPage().goto(url);
|
|
17
|
+
await getPage().goto(url, WAIT_UNTIL);
|
|
14
18
|
}
|
|
15
19
|
/** Reload the current page. */
|
|
16
20
|
export async function reload() {
|
|
17
|
-
await getPage().reload();
|
|
21
|
+
await getPage().reload(WAIT_UNTIL);
|
|
18
22
|
}
|
|
19
23
|
/** Go back in history. */
|
|
20
24
|
export async function back() {
|
|
21
|
-
await getPage().goBack();
|
|
25
|
+
await getPage().goBack(WAIT_UNTIL);
|
|
22
26
|
}
|
|
23
27
|
/** Go forward in history. */
|
|
24
28
|
export async function forward() {
|
|
25
|
-
await getPage().goForward();
|
|
29
|
+
await getPage().goForward(WAIT_UNTIL);
|
|
26
30
|
}
|
|
27
31
|
/** The current full URL (`location.href`). */
|
|
28
32
|
export function currentUrl() {
|
package/dist/navigation.js.map
CHANGED
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC;;;;;;;;GAQG;AAEH,sEAAsE;AACtE,gFAAgF;AAChF,oEAAoE;AACpE,MAAM,UAAU,GAAG,EAAE,SAAS,EAAE,kBAAkB,EAAW,CAAA;AAE7D,6CAA6C;AAC7C,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,GAAW;IACrC,MAAM,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAA;AACtC,CAAC;AAED,+BAA+B;AAC/B,MAAM,CAAC,KAAK,UAAU,MAAM;IAC3B,MAAM,OAAO,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;AACnC,CAAC;AAED,0BAA0B;AAC1B,MAAM,CAAC,KAAK,UAAU,IAAI;IACzB,MAAM,OAAO,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;AACnC,CAAC;AAED,6BAA6B;AAC7B,MAAM,CAAC,KAAK,UAAU,OAAO;IAC5B,MAAM,OAAO,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;AACtC,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"}
|
package/dist/reporter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAaH,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;AA8JD,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
CHANGED
|
@@ -18,6 +18,10 @@ import { mkdirSync, writeFileSync } from 'node:fs';
|
|
|
18
18
|
import { tmpdir } from 'node:os';
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import { parseOpiceDsn } from './dsn.js';
|
|
21
|
+
/** Per-request cap, so a hung connection can't stall a scenario's afterAll. */
|
|
22
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
23
|
+
/** Total cap on `flush()` waiting for pending step uploads (afterAll-bounded). */
|
|
24
|
+
const FLUSH_BUDGET_MS = 15_000;
|
|
21
25
|
class NoopReporter {
|
|
22
26
|
async startScenario(input) {
|
|
23
27
|
return `noop-${input.name}-${Date.now()}`;
|
|
@@ -105,7 +109,14 @@ class HttpReporter {
|
|
|
105
109
|
});
|
|
106
110
|
}
|
|
107
111
|
async flush() {
|
|
108
|
-
|
|
112
|
+
// Bound the wait: step uploads (a base64 screenshot each) pile up on a
|
|
113
|
+
// slow/contended uplink, and `flush()` is awaited in a scenario's afterAll
|
|
114
|
+
// — an unbounded wait there blows the afterAll budget and fails the
|
|
115
|
+
// scenario over *reporting*, not the test. Best-effort: stop waiting after
|
|
116
|
+
// FLUSH_BUDGET_MS; stragglers settle in the background. Pair with the
|
|
117
|
+
// per-request timeout in `fetch`.
|
|
118
|
+
const budget = new Promise((resolve) => setTimeout(resolve, FLUSH_BUDGET_MS));
|
|
119
|
+
await Promise.race([Promise.allSettled([...this.pending]), budget]);
|
|
109
120
|
// finishRun is the CLI's responsibility — see handoff file.
|
|
110
121
|
}
|
|
111
122
|
track(promise) {
|
|
@@ -131,6 +142,8 @@ class HttpReporter {
|
|
|
131
142
|
'content-type': 'application/json',
|
|
132
143
|
},
|
|
133
144
|
body: body == null ? undefined : JSON.stringify(body),
|
|
145
|
+
// Don't let a stalled connection hang past the afterAll budget.
|
|
146
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
134
147
|
});
|
|
135
148
|
}
|
|
136
149
|
catch (err) {
|
package/dist/reporter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"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"}
|
package/dist/scenario.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scenario.d.ts","sourceRoot":"","sources":["../src/scenario.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scenario.d.ts","sourceRoot":"","sources":["../src/scenario.ts"],"names":[],"mappings":"AAqBA,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,CA6DzG;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
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { closePage, launchPage } from './context.js';
|
|
3
|
+
import { closePage, getContext, launchPage } from './context.js';
|
|
4
4
|
import { screenshot } from './element.js';
|
|
5
5
|
import { getReporter } from './reporter.js';
|
|
6
|
+
import { loadUserSetup } from './setup.js';
|
|
6
7
|
/**
|
|
7
8
|
* `bun:test` is resolved lazily, at the moment `browserTest` registers a
|
|
8
9
|
* scenario — never at module load. That keeps `@opice/harness` importable
|
|
@@ -78,9 +79,19 @@ export function browserTest(name, fn, options = {}) {
|
|
|
78
79
|
currentScenarioId = null;
|
|
79
80
|
}
|
|
80
81
|
const page = await launchPage();
|
|
82
|
+
// Repo-level context setup (browser-setup.ts) runs before the first
|
|
83
|
+
// navigation, so an addInitScript it registers fires before the app's
|
|
84
|
+
// own scripts on first paint.
|
|
85
|
+
const setup = await loadUserSetup();
|
|
86
|
+
if (setup)
|
|
87
|
+
await setup(getContext());
|
|
81
88
|
const base = opts.url ?? PLAYGROUND_URL;
|
|
82
89
|
const url = opts.hash ? `${base}#${opts.hash}` : base;
|
|
83
|
-
|
|
90
|
+
// `domcontentloaded`, not the default `load`: an SPA paints after its JS
|
|
91
|
+
// runs and may hold `load` on a slow chunk or long-lived connection, so
|
|
92
|
+
// waiting for `load` flakily times out under CI contention. Readiness is
|
|
93
|
+
// handled by the test's retrying assertions.
|
|
94
|
+
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
84
95
|
}, 30_000);
|
|
85
96
|
afterAll(async () => {
|
|
86
97
|
try {
|
package/dist/scenario.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scenario.js","sourceRoot":"","sources":["../src/scenario.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"scenario.js","sourceRoot":"","sources":["../src/scenario.ts"],"names":[],"mappings":"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"}
|
package/dist/setup.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BrowserContext } from 'playwright';
|
|
2
|
+
/**
|
|
3
|
+
* Repo-level browser context setup — the context analog of `browser-tools.ts`.
|
|
4
|
+
*
|
|
5
|
+
* A repo may export a `setup(context)` from `browser-setup.ts`; opice runs it
|
|
6
|
+
* against the freshly-created `BrowserContext` **before the first navigation**,
|
|
7
|
+
* on both faces:
|
|
8
|
+
*
|
|
9
|
+
* - **tests** — `browserTest` runs it in `beforeAll`, after launching the
|
|
10
|
+
* context but before `page.goto`,
|
|
11
|
+
* - **authoring** — the `opice-browser` server runs it once after connecting,
|
|
12
|
+
* before navigating to the launch URL.
|
|
13
|
+
*
|
|
14
|
+
* Because it runs pre-navigation, an `addInitScript` registered here executes
|
|
15
|
+
* before the app's own scripts on the very first paint — the right place to
|
|
16
|
+
* seed storage/cookies, grant permissions, or set a flag the app reads at boot
|
|
17
|
+
* (e.g. "this is an automated run — don't render dev-only chrome"). Keep the
|
|
18
|
+
* body idempotent: it may run more than once over a context's life.
|
|
19
|
+
*/
|
|
20
|
+
export type BrowserSetup = (context: BrowserContext) => void | Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Locate a repo's `browser-setup.ts` (or `.js`/`.mjs`), walking up from `from`.
|
|
23
|
+
* Returns the absolute path, or null if none is found before the filesystem root.
|
|
24
|
+
*/
|
|
25
|
+
export declare function findUserSetupFile(from?: string): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Load a repo's `browser-setup.ts` and return its setup function (the `setup`
|
|
28
|
+
* named export, or the default export), or null if there is no such file or it
|
|
29
|
+
* doesn't export a function.
|
|
30
|
+
*/
|
|
31
|
+
export declare function loadUserSetup(from?: string): Promise<BrowserSetup | null>;
|
|
32
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAE5E;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAW7E;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAM/E"}
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
/**
|
|
5
|
+
* Locate a repo's `browser-setup.ts` (or `.js`/`.mjs`), walking up from `from`.
|
|
6
|
+
* Returns the absolute path, or null if none is found before the filesystem root.
|
|
7
|
+
*/
|
|
8
|
+
export function findUserSetupFile(from = process.cwd()) {
|
|
9
|
+
let dir = path.resolve(from);
|
|
10
|
+
for (;;) {
|
|
11
|
+
for (const name of ['browser-setup.ts', 'browser-setup.js', 'browser-setup.mjs']) {
|
|
12
|
+
const candidate = path.join(dir, name);
|
|
13
|
+
if (existsSync(candidate))
|
|
14
|
+
return candidate;
|
|
15
|
+
}
|
|
16
|
+
const parent = path.dirname(dir);
|
|
17
|
+
if (parent === dir)
|
|
18
|
+
return null;
|
|
19
|
+
dir = parent;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Load a repo's `browser-setup.ts` and return its setup function (the `setup`
|
|
24
|
+
* named export, or the default export), or null if there is no such file or it
|
|
25
|
+
* doesn't export a function.
|
|
26
|
+
*/
|
|
27
|
+
export async function loadUserSetup(from) {
|
|
28
|
+
const file = findUserSetupFile(from);
|
|
29
|
+
if (!file)
|
|
30
|
+
return null;
|
|
31
|
+
const mod = (await import(pathToFileURL(file).href));
|
|
32
|
+
const fn = mod['setup'] ?? mod['default'];
|
|
33
|
+
return typeof fn === 'function' ? fn : null;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAuBxC;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe,OAAO,CAAC,GAAG,EAAE;IAC7D,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,SAAS,CAAC;QACT,KAAK,MAAM,IAAI,IAAI,CAAC,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,CAAC,EAAE,CAAC;YAClF,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACtC,IAAI,UAAU,CAAC,SAAS,CAAC;gBAAE,OAAO,SAAS,CAAA;QAC5C,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAA;QAC/B,GAAG,GAAG,MAAM,CAAA;IACb,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAa;IAChD,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAA;IACpC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IACtB,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAA4B,CAAA;IAC/E,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC,CAAA;IACzC,OAAO,OAAO,EAAE,KAAK,UAAU,CAAC,CAAC,CAAE,EAAmB,CAAC,CAAC,CAAC,IAAI,CAAA;AAC9D,CAAC"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -18,6 +18,9 @@ export type { OpiceDsn } from './dsn.js'
|
|
|
18
18
|
export { command, call, runCommand, makeCtx, loadUserCommands, findUserCommandsFile, z } from './command.js'
|
|
19
19
|
export type { Command, CommandCtx } from './command.js'
|
|
20
20
|
|
|
21
|
+
export { loadUserSetup, findUserSetupFile } from './setup.js'
|
|
22
|
+
export type { BrowserSetup } from './setup.js'
|
|
23
|
+
|
|
21
24
|
// Playwright's web-first `expect` (retrying locator matchers + generic matchers)
|
|
22
25
|
// works under `bun:test`; re-export it so tests use a single `expect`.
|
|
23
26
|
export { expect } from '@playwright/test'
|
package/src/navigation.ts
CHANGED
|
@@ -10,24 +10,29 @@ import { getPage } from './context.js'
|
|
|
10
10
|
* dropped) no longer applies — `reload()` drives the page directly.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
// SPA pages can hold the `load` event on a slow chunk or a long-lived
|
|
14
|
+
// connection, so every navigation waits for `domcontentloaded` (not the default
|
|
15
|
+
// `load`) and lets the test's retrying assertions handle readiness.
|
|
16
|
+
const WAIT_UNTIL = { waitUntil: 'domcontentloaded' } as const
|
|
17
|
+
|
|
13
18
|
/** Navigate to a URL in the current page. */
|
|
14
19
|
export async function open(url: string): Promise<void> {
|
|
15
|
-
await getPage().goto(url)
|
|
20
|
+
await getPage().goto(url, WAIT_UNTIL)
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
/** Reload the current page. */
|
|
19
24
|
export async function reload(): Promise<void> {
|
|
20
|
-
await getPage().reload()
|
|
25
|
+
await getPage().reload(WAIT_UNTIL)
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
/** Go back in history. */
|
|
24
29
|
export async function back(): Promise<void> {
|
|
25
|
-
await getPage().goBack()
|
|
30
|
+
await getPage().goBack(WAIT_UNTIL)
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
/** Go forward in history. */
|
|
29
34
|
export async function forward(): Promise<void> {
|
|
30
|
-
await getPage().goForward()
|
|
35
|
+
await getPage().goForward(WAIT_UNTIL)
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
/** The current full URL (`location.href`). */
|
package/src/reporter.ts
CHANGED
|
@@ -20,6 +20,11 @@ import { tmpdir } from 'node:os'
|
|
|
20
20
|
import path from 'node:path'
|
|
21
21
|
import { parseOpiceDsn } from './dsn.js'
|
|
22
22
|
|
|
23
|
+
/** Per-request cap, so a hung connection can't stall a scenario's afterAll. */
|
|
24
|
+
const REQUEST_TIMEOUT_MS = 10_000
|
|
25
|
+
/** Total cap on `flush()` waiting for pending step uploads (afterAll-bounded). */
|
|
26
|
+
const FLUSH_BUDGET_MS = 15_000
|
|
27
|
+
|
|
23
28
|
export interface ReporterConfig {
|
|
24
29
|
endpoint: string
|
|
25
30
|
projectId: string
|
|
@@ -161,7 +166,14 @@ class HttpReporter implements Reporter {
|
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
async flush(): Promise<void> {
|
|
164
|
-
|
|
169
|
+
// Bound the wait: step uploads (a base64 screenshot each) pile up on a
|
|
170
|
+
// slow/contended uplink, and `flush()` is awaited in a scenario's afterAll
|
|
171
|
+
// — an unbounded wait there blows the afterAll budget and fails the
|
|
172
|
+
// scenario over *reporting*, not the test. Best-effort: stop waiting after
|
|
173
|
+
// FLUSH_BUDGET_MS; stragglers settle in the background. Pair with the
|
|
174
|
+
// per-request timeout in `fetch`.
|
|
175
|
+
const budget = new Promise<void>((resolve) => setTimeout(resolve, FLUSH_BUDGET_MS))
|
|
176
|
+
await Promise.race([Promise.allSettled([...this.pending]), budget])
|
|
165
177
|
// finishRun is the CLI's responsibility — see handoff file.
|
|
166
178
|
}
|
|
167
179
|
|
|
@@ -189,6 +201,8 @@ class HttpReporter implements Reporter {
|
|
|
189
201
|
'content-type': 'application/json',
|
|
190
202
|
},
|
|
191
203
|
body: body == null ? undefined : JSON.stringify(body),
|
|
204
|
+
// Don't let a stalled connection hang past the afterAll budget.
|
|
205
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
192
206
|
})
|
|
193
207
|
} catch (err) {
|
|
194
208
|
// Network error / blocked request (e.g. a test runner that installs a
|
package/src/scenario.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createRequire } from 'node:module'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
-
import { closePage, launchPage } from './context.js'
|
|
3
|
+
import { closePage, getContext, launchPage } from './context.js'
|
|
4
4
|
import { screenshot } from './element.js'
|
|
5
5
|
import { getReporter } from './reporter.js'
|
|
6
|
+
import { loadUserSetup } from './setup.js'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* `bun:test` is resolved lazily, at the moment `browserTest` registers a
|
|
@@ -94,9 +95,18 @@ export function browserTest(name: string, fn: () => void, options: BrowserTestOp
|
|
|
94
95
|
currentScenarioId = null
|
|
95
96
|
}
|
|
96
97
|
const page = await launchPage()
|
|
98
|
+
// Repo-level context setup (browser-setup.ts) runs before the first
|
|
99
|
+
// navigation, so an addInitScript it registers fires before the app's
|
|
100
|
+
// own scripts on first paint.
|
|
101
|
+
const setup = await loadUserSetup()
|
|
102
|
+
if (setup) await setup(getContext())
|
|
97
103
|
const base = opts.url ?? PLAYGROUND_URL
|
|
98
104
|
const url = opts.hash ? `${base}#${opts.hash}` : base
|
|
99
|
-
|
|
105
|
+
// `domcontentloaded`, not the default `load`: an SPA paints after its JS
|
|
106
|
+
// runs and may hold `load` on a slow chunk or long-lived connection, so
|
|
107
|
+
// waiting for `load` flakily times out under CI contention. Readiness is
|
|
108
|
+
// handled by the test's retrying assertions.
|
|
109
|
+
await page.goto(url, { waitUntil: 'domcontentloaded' })
|
|
100
110
|
}, 30_000)
|
|
101
111
|
|
|
102
112
|
afterAll(async () => {
|
package/src/setup.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
4
|
+
import type { BrowserContext } from 'playwright'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Repo-level browser context setup — the context analog of `browser-tools.ts`.
|
|
8
|
+
*
|
|
9
|
+
* A repo may export a `setup(context)` from `browser-setup.ts`; opice runs it
|
|
10
|
+
* against the freshly-created `BrowserContext` **before the first navigation**,
|
|
11
|
+
* on both faces:
|
|
12
|
+
*
|
|
13
|
+
* - **tests** — `browserTest` runs it in `beforeAll`, after launching the
|
|
14
|
+
* context but before `page.goto`,
|
|
15
|
+
* - **authoring** — the `opice-browser` server runs it once after connecting,
|
|
16
|
+
* before navigating to the launch URL.
|
|
17
|
+
*
|
|
18
|
+
* Because it runs pre-navigation, an `addInitScript` registered here executes
|
|
19
|
+
* before the app's own scripts on the very first paint — the right place to
|
|
20
|
+
* seed storage/cookies, grant permissions, or set a flag the app reads at boot
|
|
21
|
+
* (e.g. "this is an automated run — don't render dev-only chrome"). Keep the
|
|
22
|
+
* body idempotent: it may run more than once over a context's life.
|
|
23
|
+
*/
|
|
24
|
+
export type BrowserSetup = (context: BrowserContext) => void | Promise<void>
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Locate a repo's `browser-setup.ts` (or `.js`/`.mjs`), walking up from `from`.
|
|
28
|
+
* Returns the absolute path, or null if none is found before the filesystem root.
|
|
29
|
+
*/
|
|
30
|
+
export function findUserSetupFile(from: string = process.cwd()): string | null {
|
|
31
|
+
let dir = path.resolve(from)
|
|
32
|
+
for (;;) {
|
|
33
|
+
for (const name of ['browser-setup.ts', 'browser-setup.js', 'browser-setup.mjs']) {
|
|
34
|
+
const candidate = path.join(dir, name)
|
|
35
|
+
if (existsSync(candidate)) return candidate
|
|
36
|
+
}
|
|
37
|
+
const parent = path.dirname(dir)
|
|
38
|
+
if (parent === dir) return null
|
|
39
|
+
dir = parent
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load a repo's `browser-setup.ts` and return its setup function (the `setup`
|
|
45
|
+
* named export, or the default export), or null if there is no such file or it
|
|
46
|
+
* doesn't export a function.
|
|
47
|
+
*/
|
|
48
|
+
export async function loadUserSetup(from?: string): Promise<BrowserSetup | null> {
|
|
49
|
+
const file = findUserSetupFile(from)
|
|
50
|
+
if (!file) return null
|
|
51
|
+
const mod = (await import(pathToFileURL(file).href)) as Record<string, unknown>
|
|
52
|
+
const fn = mod['setup'] ?? mod['default']
|
|
53
|
+
return typeof fn === 'function' ? (fn as BrowserSetup) : null
|
|
54
|
+
}
|