@opice/harness 0.3.0 → 0.4.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 CHANGED
@@ -19,7 +19,7 @@ Runs under the Bun test runner.
19
19
  import { test, describe } from 'bun:test'
20
20
  import { browserTest, el, byRole, byLabel, step, expect } from '@opice/harness'
21
21
 
22
- browserTest('DataGrid', () => {
22
+ browserTest({ name: 'DataGrid', hash: 'datagrid' }, () => {
23
23
  test('renders and is interactive', async () => {
24
24
  await step('table is visible', async () => {
25
25
  await expect(el('datagrid-table')).toBeVisible()
@@ -30,7 +30,7 @@ browserTest('DataGrid', () => {
30
30
  await expect(el('datagrid-row-0')).toHaveAttribute('data-highlighted', '')
31
31
  })
32
32
  }, 60_000)
33
- }, { hash: 'datagrid' })
33
+ })
34
34
  ```
35
35
 
36
36
  The DSL is **async** and returns Playwright `Locator`s, so the full Locator API
@@ -1,6 +1,8 @@
1
1
  import type { Locator, Page } from 'playwright';
2
2
  /** The ARIA role union accepted by Playwright's `getByRole`. */
3
3
  type Role = Parameters<Page['getByRole']>[0];
4
+ /** Playwright's `getByRole` options minus `name` (which is passed positionally). */
5
+ type RoleOptions = Omit<NonNullable<Parameters<Page['getByRole']>[1]>, 'name'>;
4
6
  /**
5
7
  * Accessible-name selectors — `byRole` / `byLabel` / `byText`.
6
8
  *
@@ -17,9 +19,14 @@ type Role = Parameters<Page['getByRole']>[0];
17
19
  */
18
20
  /**
19
21
  * Find an element by ARIA role and (optionally) its accessible name.
20
- * `name` does a substring, case-insensitive match by default.
22
+ * `name` does a substring, case-insensitive match when a string; pass a
23
+ * `RegExp` to match the accessible name by pattern (e.g. a generated id).
24
+ *
25
+ * `options` forwards the rest of Playwright's `getByRole` filters — most useful
26
+ * is `level` to pin a heading (`byRole('heading', 'Title', { level: 2 })`), plus
27
+ * `exact`, `checked`, `pressed`, `expanded`, `disabled`, etc.
21
28
  */
22
- export declare function byRole(role: Role, name?: string): Locator;
29
+ export declare function byRole(role: Role, name?: string | RegExp, options?: RoleOptions): Locator;
23
30
  /** Find a form control by its associated `<label>` (or `aria-label`) text. */
24
31
  export declare function byLabel(text: string): Locator;
25
32
  /** Find an element by its visible text (substring, case-insensitive). */
@@ -1 +1 @@
1
- {"version":3,"file":"accessible.d.ts","sourceRoot":"","sources":["../src/accessible.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAG/C,gEAAgE;AAChE,KAAK,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAE5C;;;;;;;;;;;;;GAaG;AAEH;;;GAGG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,8EAA8E;AAC9E,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED,yEAAyE;AACzE,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5C"}
1
+ {"version":3,"file":"accessible.d.ts","sourceRoot":"","sources":["../src/accessible.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAG/C,gEAAgE;AAChE,KAAK,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAC5C,oFAAoF;AACpF,KAAK,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;AAE9E;;;;;;;;;;;;;GAaG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAGzF;AAED,8EAA8E;AAC9E,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED,yEAAyE;AACzE,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5C"}
@@ -15,10 +15,16 @@ import { getPage } from './context.js';
15
15
  */
16
16
  /**
17
17
  * Find an element by ARIA role and (optionally) its accessible name.
18
- * `name` does a substring, case-insensitive match by default.
18
+ * `name` does a substring, case-insensitive match when a string; pass a
19
+ * `RegExp` to match the accessible name by pattern (e.g. a generated id).
20
+ *
21
+ * `options` forwards the rest of Playwright's `getByRole` filters — most useful
22
+ * is `level` to pin a heading (`byRole('heading', 'Title', { level: 2 })`), plus
23
+ * `exact`, `checked`, `pressed`, `expanded`, `disabled`, etc.
19
24
  */
20
- export function byRole(role, name) {
21
- return getPage().getByRole(role, name == null ? undefined : { name });
25
+ export function byRole(role, name, options) {
26
+ const opts = { ...options, ...(name == null ? {} : { name }) };
27
+ return getPage().getByRole(role, Object.keys(opts).length > 0 ? opts : undefined);
22
28
  }
23
29
  /** Find a form control by its associated `<label>` (or `aria-label`) text. */
24
30
  export function byLabel(text) {
@@ -1 +1 @@
1
- {"version":3,"file":"accessible.js","sourceRoot":"","sources":["../src/accessible.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAKtC;;;;;;;;;;;;;GAaG;AAEH;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,IAAU,EAAE,IAAa;IAC/C,OAAO,OAAO,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;AACtE,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,OAAO,CAAC,IAAY;IACnC,OAAO,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;AAClC,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,MAAM,CAAC,IAAY;IAClC,OAAO,OAAO,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;AACjC,CAAC"}
1
+ {"version":3,"file":"accessible.js","sourceRoot":"","sources":["../src/accessible.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAOtC;;;;;;;;;;;;;GAaG;AAEH;;;;;;;;GAQG;AACH,MAAM,UAAU,MAAM,CAAC,IAAU,EAAE,IAAsB,EAAE,OAAqB;IAC/E,MAAM,IAAI,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAA;IAC9D,OAAO,OAAO,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;AAClF,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,OAAO,CAAC,IAAY;IACnC,OAAO,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;AAClC,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,MAAM,CAAC,IAAY;IAClC,OAAO,OAAO,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;AACjC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ export { el, tid, waitFor, wait, evalJs, screenshot } from './element.js';
2
2
  export { byLabel, byRole, byText } from './accessible.js';
3
3
  export { back, currentPath, currentUrl, forward, open, reload } from './navigation.js';
4
4
  export { getPage, getContext } from './context.js';
5
- export { browserTest, step } from './scenario.js';
6
- export type { BrowserTestOptions } from './scenario.js';
5
+ export { browserTest, invariant, step } from './scenario.js';
6
+ export type { BrowserTestMeta, StepContract } from './scenario.js';
7
7
  export { getReporter, setReporter, configureFromEnv } from './reporter.js';
8
8
  export type { Reporter, ReporterConfig, StepEvent, ScenarioStart, ScenarioFinish } from './reporter.js';
9
9
  export { parseOpiceDsn } from './dsn.js';
@@ -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;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"}
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,SAAS,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAC5D,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAElE,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
@@ -2,7 +2,7 @@ export { el, tid, waitFor, wait, evalJs, screenshot } from './element.js';
2
2
  export { byLabel, byRole, byText } from './accessible.js';
3
3
  export { back, currentPath, currentUrl, forward, open, reload } from './navigation.js';
4
4
  export { getPage, getContext } from './context.js';
5
- export { browserTest, step } from './scenario.js';
5
+ export { browserTest, invariant, 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';
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,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAG7D,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,SAAS,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAG5D,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"}
@@ -26,16 +26,27 @@ export interface StepEvent {
26
26
  scenarioId: string;
27
27
  /** Authoring order within the scenario, assigned at step() call time. */
28
28
  sequence: number;
29
+ /**
30
+ * 'step' (a procedural step) or 'invariant' (a scenario-level acceptance).
31
+ * The platform may render invariants distinctly; older workers ignore it.
32
+ */
33
+ kind?: 'step' | 'invariant';
29
34
  name: string;
30
35
  /**
31
36
  * 'fixme' (a step.fixme that failed, as expected) and 'fixmepass' (a
32
37
  * step.fixme that unexpectedly passed) are tolerated warnings — neither
33
- * fails the scenario.
38
+ * fails the scenario. 'pending' is a phase-1 stub that never ran (no body
39
+ * yet); a scenario carrying one reads as 'incomplete'.
34
40
  */
35
- status: 'passed' | 'failed' | 'fixme' | 'fixmepass';
41
+ status: 'passed' | 'failed' | 'fixme' | 'fixmepass' | 'pending';
36
42
  durationMs: number;
37
43
  error?: string;
38
- /** Mandatory note from step.fixme — why the failure is tolerated. */
44
+ /**
45
+ * Durable rationale carried from the unit's contract (phase-1 `intent`) —
46
+ * why it exists / what it proves. Surfaced on the dashboard.
47
+ */
48
+ intent?: string;
49
+ /** Mandatory note from .fixme — why the failure is tolerated. */
39
50
  reason?: string;
40
51
  screenshotPath?: string;
41
52
  }
@@ -43,7 +54,12 @@ export interface ScenarioStart {
43
54
  name: string;
44
55
  hash?: string;
45
56
  testFile?: string;
46
- scenarioFile?: string;
57
+ /** Requirement / feature id this scenario covers (grouping). */
58
+ feature?: string;
59
+ /** Seeds required for the scenario (machine-checkable preconditions). */
60
+ seeds?: string[];
61
+ /** Identities / roles the scenario acts as. */
62
+ roles?: string[];
47
63
  }
48
64
  export interface ScenarioFinish {
49
65
  scenarioId: string;
@@ -1 +1 @@
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;;;;OAIG;IACH,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,CAAA;IACnD,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,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;AA+JD,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"}
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;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,GAAG,SAAS,CAAA;IAC/D,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,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,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;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;AAmKD,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
@@ -73,7 +73,9 @@ class HttpReporter {
73
73
  name: input.name,
74
74
  hash: input.hash,
75
75
  testFile: input.testFile,
76
- scenarioFile: input.scenarioFile,
76
+ feature: input.feature,
77
+ seeds: input.seeds,
78
+ roles: input.roles,
77
79
  });
78
80
  return response['scenarioId'];
79
81
  }
@@ -92,10 +94,12 @@ class HttpReporter {
92
94
  : undefined;
93
95
  await this.fetch('POST', `/api/v1/runs/${runId}/scenarios/${event.scenarioId}/steps`, {
94
96
  sequence: event.sequence,
97
+ kind: event.kind,
95
98
  name: event.name,
96
99
  status: event.status,
97
100
  durationMs: event.durationMs,
98
101
  error: event.error,
102
+ intent: event.intent,
99
103
  reason: event.reason,
100
104
  screenshot,
101
105
  });
@@ -1 +1 @@
1
- {"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"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"}
@@ -1,24 +1,85 @@
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). */
1
+ /**
2
+ * Scenario metadata the **first** argument to `browserTest`.
3
+ *
4
+ * This is the durable, machine-relevant context an opice scenario carries
5
+ * independent of its concrete steps: where it runs, what it presupposes, what
6
+ * requirement it covers. It is written in **phase 1** (planning, `opice-plan`)
7
+ * and preserved through **phase 2** (authoring, `opice-author`) — the scenario
8
+ * file IS the spec, so this metadata never lives in a separate `.md` that can
9
+ * drift from the test.
10
+ *
11
+ * The rule of thumb for what belongs here vs. a code comment: *does anything
12
+ * other than a human read it?* Seeds (a precondition a runner could verify),
13
+ * the feature id (grouping on the dashboard), the acting roles — yes, so they
14
+ * are first-class fields. Background rationale that only a human reads stays a
15
+ * comment next to the relevant step.
16
+ */
17
+ export interface BrowserTestMeta {
18
+ /** Scenario name — becomes the `describe()` title. Required. */
19
+ name: string;
20
+ /** Override base URL (defaults to the `PLAYGROUND_URL` env var). */
5
21
  url?: string;
22
+ /** Hash fragment appended to the base URL (e.g. `'datagrid'`). */
23
+ hash?: string;
24
+ /** Feature / requirement id this scenario covers (e.g. `'F-SML-03a'`). */
25
+ feature?: string;
6
26
  /**
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`.
27
+ * Seeds that must be loaded for this scenario to run — machine-checkable
28
+ * preconditions, not prose. e.g. `['initial-data', 'crm-master-data']`.
10
29
  */
11
- scenarioFile?: string;
30
+ seeds?: string[];
31
+ /** Identities / roles the scenario acts as, e.g. `['crmOperator']`. */
32
+ roles?: string[];
12
33
  }
13
34
  /**
14
35
  * Register a top-level browser test scenario.
15
36
  *
16
- * Each `browserTest(name, fn)` launches its own isolated Playwright browser +
37
+ * Each `browserTest(meta, fn)` launches its own isolated Playwright browser +
17
38
  * context + page, navigates to the playground URL, runs the given `fn` (which
18
39
  * typically contains nested `describe`/`test` blocks), and tears the browser
19
40
  * down in `afterAll`.
41
+ *
42
+ * Metadata is the **first** argument (`{ name, url, hash, feature, seeds,
43
+ * roles }`); `name` is required.
20
44
  */
21
- export declare function browserTest(name: string, fn: () => void, options?: BrowserTestOptions | string): void;
45
+ export declare function browserTest(meta: BrowserTestMeta, fn: () => void): void;
46
+ /**
47
+ * The durable contract of a step or invariant, separate from its mechanics.
48
+ *
49
+ * `intent` is written in **phase 1** and survives **verbatim** into the
50
+ * authored test — it's the "why this exists / what it proves", the independent
51
+ * statement of intent that the concrete body is checked against. `hint` is
52
+ * phase-1 scaffolding *for the authoring agent* ("what to actually do here");
53
+ * it is consumed when the step is authored and dropped once a body exists.
54
+ */
55
+ export interface StepContract {
56
+ /** Durable rationale: why this step/invariant exists, what it proves. */
57
+ intent?: string;
58
+ /**
59
+ * Phase-1 instruction to the authoring agent — what to do on the page here.
60
+ * Ephemeral: drop it once the body is written.
61
+ */
62
+ hint?: string;
63
+ }
64
+ type StepBody = () => void | Promise<void>;
65
+ interface StepFn {
66
+ /** Executable step. */
67
+ (name: string, fn: StepBody): Promise<void>;
68
+ /** Phase-1 stub: a step with a contract but no body yet (status: pending). */
69
+ (name: string, contract: StepContract): Promise<void>;
70
+ /** Authored step that keeps its durable contract. */
71
+ (name: string, contract: StepContract, fn: StepBody): Promise<void>;
72
+ }
73
+ interface StepExtras {
74
+ fixme: (name: string, reason: string, fn: StepBody) => Promise<void>;
75
+ /**
76
+ * A **blocked** pending stub: the step can't be authored yet because the app
77
+ * feature it covers isn't implemented. Reports as 'pending' with the reason
78
+ * attached (the dashboard shows it as blocked, distinct from a plain stub
79
+ * that's merely awaiting a test). `reason` is mandatory — say what's missing.
80
+ */
81
+ blocked: (name: string, reason: string, contract?: StepContract) => Promise<void>;
82
+ }
22
83
  /**
23
84
  * A reportable step inside a scenario. Captures duration + screenshot on
24
85
  * finish, forwards to the active reporter (no-op unless configured via env).
@@ -26,6 +87,14 @@ export declare function browserTest(name: string, fn: () => void, options?: Brow
26
87
  * The body may be sync or async; `step` always returns a promise, so call it
27
88
  * with `await step('…', async () => { … })`.
28
89
  *
90
+ * Three forms:
91
+ * - `step(name, fn)` — executable step (the common case).
92
+ * - `step(name, { intent, hint })` — a **pending** phase-1 stub: declares the
93
+ * step's intent and what to do, but has no body yet. It does not run and
94
+ * reports as `pending`; `opice-author` fills in the body.
95
+ * - `step(name, { intent }, fn)` — an authored step that keeps its durable
96
+ * `intent` (preserved verbatim from phase 1) alongside the body.
97
+ *
29
98
  * `step.fixme(name, reason, fn)` marks a **known, tolerated failure**: the body
30
99
  * still runs, but a failure inside it does NOT fail the scenario or the CI run —
31
100
  * it's reported as an amber warning instead. The `reason` is mandatory (use it
@@ -33,8 +102,41 @@ export declare function browserTest(name: string, fn: () => void, options?: Brow
33
102
  * step unexpectedly *passes*, it's flagged too ('fixmepass') so a stale marker
34
103
  * doesn't linger. Unlike Playwright's `test.fixme()`, which **skips** the test,
35
104
  * `step.fixme` **runs** it — the mandatory reason is there to keep them apart.
105
+ *
106
+ * `step.blocked(name, reason, contract?)` is a pending stub that **can't be
107
+ * authored yet** because the app feature doesn't exist — distinct from a plain
108
+ * `step(name, contract)` stub that's simply awaiting a test. Both report as
109
+ * 'pending' (scenario reads 'incomplete'); the blocked one carries its reason.
110
+ */
111
+ export declare const step: StepFn & StepExtras;
112
+ /**
113
+ * A scenario-level **invariant** — an acceptance property the scenario
114
+ * enforces, independent of the procedural steps. This is the durable "what
115
+ * must be true" that used to live in a scenario's prose Notes; expressing it as
116
+ * a call keeps it in the one source of truth (the test) instead of a separate
117
+ * `.md` that drifts.
118
+ *
119
+ * A failing `invariant` fails the scenario like any hard assertion — it's an
120
+ * acceptance, not a nicety.
121
+ *
122
+ * - `invariant(name, fn)` — enforced now.
123
+ * - `invariant.todo(name, hint?)` — phase-1 stub: states the acceptance but
124
+ * isn't wired yet (status: pending). `opice-author` promotes it to an
125
+ * enforced `invariant(...)` (or an `invariant.fixme(...)` if it can't hold
126
+ * yet) once it knows how to check it.
127
+ * - `invariant.blocked(name, reason)` — a pending acceptance that can't be
128
+ * wired yet because the feature it guards isn't implemented (vs `.todo`,
129
+ * which is merely awaiting authoring). Reports 'pending' with the reason.
130
+ * - `invariant.fixme(name, reason, fn)` — a known-unenforceable acceptance,
131
+ * tolerated like `step.fixme` (e.g. a security property deferred to a
132
+ * ticket). The body runs and is expected to fail; the failure is reported as
133
+ * an amber warning and neither fails the scenario nor the run.
36
134
  */
37
- export declare const step: ((name: string, fn: () => void | Promise<void>) => Promise<void>) & {
38
- fixme: (name: string, reason: string, fn: () => void | Promise<void>) => Promise<void>;
135
+ export declare const invariant: {
136
+ (name: string, fn: StepBody): Promise<void>;
137
+ todo: (name: string, hint?: string) => Promise<void>;
138
+ blocked: (name: string, reason: string) => Promise<void>;
139
+ fixme: (name: string, reason: string, fn: StepBody) => Promise<void>;
39
140
  };
141
+ export {};
40
142
  //# sourceMappingURL=scenario.d.ts.map
@@ -1 +1 @@
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;AAsDD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,IAAI,UACT,MAAM,MAAM,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;kBAE9C,MAAM,UAAU,MAAM,MAAM,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;CAGrF,CAAA"}
1
+ {"version":3,"file":"scenario.d.ts","sourceRoot":"","sources":["../src/scenario.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,eAAe;IAC/B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAA;IACZ,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAmCD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAwHvE;AAKD;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC5B,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;CACb;AA6FD,KAAK,QAAQ,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAE1C,UAAU,MAAM;IACf,uBAAuB;IACvB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3C,8EAA8E;IAC9E,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrD,qDAAqD;IACrD,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACnE;AAED,UAAU,UAAU;IACnB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpE;;;;;OAKG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACjF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,IAAI,EAAE,MAAM,GAAG,UAa3B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,SAAS,EAAE;IACvB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3C,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAWpE,CAAA"}
package/dist/scenario.js CHANGED
@@ -40,14 +40,10 @@ function captureTestFile() {
40
40
  }
41
41
  return undefined;
42
42
  }
43
- function defaultScenarioFile(testFile) {
44
- if (!testFile)
45
- return undefined;
46
- return testFile.replace(/\.test\.[tj]sx?$/, '.scenario.md');
47
- }
48
43
  let currentScenarioId = null;
49
44
  let currentScenarioStart = 0;
50
45
  let currentScenarioFailures = 0;
46
+ let currentScenarioPending = 0;
51
47
  // Monotonic per-scenario step counter. Assigned synchronously at each step()
52
48
  // call so order reflects authoring order — step records are POSTed
53
49
  // fire-and-forget and would otherwise be sequenced by arrival order at the
@@ -56,42 +52,94 @@ let currentScenarioStepSeq = 0;
56
52
  /**
57
53
  * Register a top-level browser test scenario.
58
54
  *
59
- * Each `browserTest(name, fn)` launches its own isolated Playwright browser +
55
+ * Each `browserTest(meta, fn)` launches its own isolated Playwright browser +
60
56
  * context + page, navigates to the playground URL, runs the given `fn` (which
61
57
  * typically contains nested `describe`/`test` blocks), and tears the browser
62
58
  * down in `afterAll`.
59
+ *
60
+ * Metadata is the **first** argument (`{ name, url, hash, feature, seeds,
61
+ * roles }`); `name` is required.
63
62
  */
64
- export function browserTest(name, fn, options = {}) {
65
- const opts = typeof options === 'string' ? { hash: options } : options;
63
+ export function browserTest(meta, fn) {
64
+ if (typeof meta === 'string') {
65
+ // Migration aid: the old signature was `browserTest(name, fn, options)`.
66
+ throw new Error('opice: browserTest now takes metadata first — browserTest({ name, url, hash, … }, fn). '
67
+ + `Got a string name (${JSON.stringify(meta)}); wrap it: browserTest({ name: ${JSON.stringify(meta)} }, fn).`);
68
+ }
69
+ if (!meta?.name) {
70
+ throw new Error('opice: browserTest requires a `name` in its metadata — browserTest({ name: "…" }, fn).');
71
+ }
66
72
  const reporter = getReporter();
67
73
  const testFile = captureTestFile();
68
- const scenarioFile = opts.scenarioFile ?? defaultScenarioFile(testFile);
69
74
  const { describe, beforeAll, afterAll } = bunTest();
70
- describe(name, () => {
75
+ describe(meta.name, () => {
71
76
  beforeAll(async () => {
72
77
  currentScenarioStart = Date.now();
73
78
  currentScenarioFailures = 0;
79
+ currentScenarioPending = 0;
74
80
  currentScenarioStepSeq = 0;
75
81
  try {
76
- currentScenarioId = await reporter.startScenario({ name, hash: opts.hash, testFile, scenarioFile });
82
+ currentScenarioId = await reporter.startScenario({
83
+ name: meta.name,
84
+ hash: meta.hash,
85
+ testFile,
86
+ feature: meta.feature,
87
+ seeds: meta.seeds,
88
+ roles: meta.roles,
89
+ });
77
90
  }
78
91
  catch {
79
92
  currentScenarioId = null;
80
93
  }
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());
88
- const base = opts.url ?? PLAYGROUND_URL;
89
- const url = opts.hash ? `${base}#${opts.hash}` : base;
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' });
94
+ try {
95
+ const page = await launchPage();
96
+ // Repo-level context setup (browser-setup.ts) runs before the first
97
+ // navigation, so an addInitScript it registers fires before the app's
98
+ // own scripts on first paint.
99
+ const setup = await loadUserSetup();
100
+ if (setup)
101
+ await setup(getContext());
102
+ const base = meta.url ?? PLAYGROUND_URL;
103
+ const url = meta.hash ? `${base}#${meta.hash}` : base;
104
+ // `domcontentloaded`, not the default `load`: an SPA paints after its JS
105
+ // runs and may hold `load` on a slow chunk or long-lived connection, so
106
+ // waiting for `load` flakily times out under CI contention. Readiness is
107
+ // handled by the test's retrying assertions.
108
+ await page.goto(url, { waitUntil: 'domcontentloaded' });
109
+ }
110
+ catch (e) {
111
+ // Setup failed before any step ran (e.g. a wrong playground URL whose
112
+ // goto is refused). bun:test does NOT run afterAll when beforeAll
113
+ // throws, so the scenario we already started above would otherwise
114
+ // sit on the dashboard as 'running' forever (see reporter.ts) —
115
+ // invisible as a failure even though CI is red. Record a synthetic
116
+ // failed step so the dashboard shows *why*, finish the scenario as
117
+ // failed, then re-throw so the run still fails.
118
+ currentScenarioFailures++;
119
+ if (currentScenarioId) {
120
+ const error = e instanceof Error ? e.message : String(e);
121
+ const durationMs = Date.now() - currentScenarioStart;
122
+ try {
123
+ await reporter.recordStep({
124
+ scenarioId: currentScenarioId,
125
+ sequence: currentScenarioStepSeq++,
126
+ kind: 'step',
127
+ name: 'scenario setup',
128
+ status: 'failed',
129
+ durationMs,
130
+ error,
131
+ });
132
+ await reporter.finishScenario({ scenarioId: currentScenarioId, status: 'failed', durationMs });
133
+ }
134
+ catch {
135
+ // best-effort: reporting the failure must never mask the
136
+ // original setup error we're about to re-throw.
137
+ }
138
+ // Null it so afterAll (should it run) doesn't double-finish.
139
+ currentScenarioId = null;
140
+ }
141
+ throw e;
142
+ }
95
143
  }, 30_000);
96
144
  afterAll(async () => {
97
145
  try {
@@ -100,6 +148,14 @@ export function browserTest(name, fn, options = {}) {
100
148
  catch {
101
149
  // ignore close errors
102
150
  }
151
+ // A scenario still carrying unfilled (pending) steps is a phase-1
152
+ // skeleton that was run before authoring. It's not a failure, but it's
153
+ // not done either — make it loud so a half-authored test isn't mistaken
154
+ // for a passing one.
155
+ if (currentScenarioPending > 0) {
156
+ console.warn(`[opice] scenario "${meta.name}" has ${currentScenarioPending} pending step(s) — `
157
+ + 'authored skeleton, not yet filled in by opice-author. The body did NOT run.');
158
+ }
103
159
  if (currentScenarioId) {
104
160
  // Drain pending step records (incl. their screenshot uploads)
105
161
  // before marking the scenario done. step() fires recordStep
@@ -125,17 +181,40 @@ export function browserTest(name, fn, options = {}) {
125
181
  fn();
126
182
  });
127
183
  }
128
- async function runStep(name, fn, fixmeReason) {
184
+ async function runUnit(unit) {
129
185
  const reporter = getReporter();
130
186
  // Capture order at call time, before the fire-and-forget record below.
131
187
  const sequence = currentScenarioStepSeq++;
188
+ // A reason *with* a body is a .fixme (tolerated failure). A reason *without*
189
+ // a body is .blocked (a pending stub that can't be authored yet).
190
+ const fixme = unit.reason !== undefined && unit.fn !== undefined;
191
+ // Phase-1 stub: no body to run. Report it as 'pending' (so the dashboard
192
+ // shows the skeleton — a scenario carrying one reads as 'incomplete') and
193
+ // count it so afterAll can warn. A `reason` here marks it 'blocked' (the
194
+ // feature isn't built); no reason is a plain todo awaiting authoring. No
195
+ // screenshot, zero duration.
196
+ if (!unit.fn) {
197
+ currentScenarioPending++;
198
+ if (currentScenarioId) {
199
+ void reporter.recordStep({
200
+ scenarioId: currentScenarioId,
201
+ sequence,
202
+ kind: unit.kind,
203
+ name: unit.name,
204
+ status: 'pending',
205
+ durationMs: 0,
206
+ intent: unit.contract?.intent,
207
+ reason: unit.reason,
208
+ });
209
+ }
210
+ return;
211
+ }
132
212
  const start = Date.now();
133
- const fixme = fixmeReason !== undefined;
134
213
  let status = 'passed';
135
214
  let error;
136
215
  try {
137
- await fn();
138
- // A fixme step that *passes* is a stale marker: surface it as an
216
+ await unit.fn();
217
+ // A fixme unit that *passes* is a stale marker: surface it as a
139
218
  // 'fixmepass' warning so the author knows they can drop the marker,
140
219
  // rather than letting it pass silently.
141
220
  if (fixme)
@@ -146,7 +225,7 @@ async function runStep(name, fn, fixmeReason) {
146
225
  if (fixme) {
147
226
  // Known / tolerated failure. Record it as 'fixme', but DON'T count it
148
227
  // toward scenario failures and DON'T re-throw — that's the whole point
149
- // of step.fixme: the scenario (and the CI run) stay green, the failure
228
+ // of .fixme: the scenario (and the CI run) stay green, the failure
150
229
  // surfaces as an amber warning on the dashboard.
151
230
  status = 'fixme';
152
231
  }
@@ -169,11 +248,13 @@ async function runStep(name, fn, fixmeReason) {
169
248
  void reporter.recordStep({
170
249
  scenarioId: currentScenarioId,
171
250
  sequence,
172
- name,
251
+ kind: unit.kind,
252
+ name: unit.name,
173
253
  status,
174
254
  durationMs,
175
255
  error,
176
- reason: fixmeReason,
256
+ intent: unit.contract?.intent,
257
+ reason: unit.reason,
177
258
  screenshotPath,
178
259
  });
179
260
  }
@@ -186,6 +267,14 @@ async function runStep(name, fn, fixmeReason) {
186
267
  * The body may be sync or async; `step` always returns a promise, so call it
187
268
  * with `await step('…', async () => { … })`.
188
269
  *
270
+ * Three forms:
271
+ * - `step(name, fn)` — executable step (the common case).
272
+ * - `step(name, { intent, hint })` — a **pending** phase-1 stub: declares the
273
+ * step's intent and what to do, but has no body yet. It does not run and
274
+ * reports as `pending`; `opice-author` fills in the body.
275
+ * - `step(name, { intent }, fn)` — an authored step that keeps its durable
276
+ * `intent` (preserved verbatim from phase 1) alongside the body.
277
+ *
189
278
  * `step.fixme(name, reason, fn)` marks a **known, tolerated failure**: the body
190
279
  * still runs, but a failure inside it does NOT fail the scenario or the CI run —
191
280
  * it's reported as an amber warning instead. The `reason` is mandatory (use it
@@ -193,8 +282,47 @@ async function runStep(name, fn, fixmeReason) {
193
282
  * step unexpectedly *passes*, it's flagged too ('fixmepass') so a stale marker
194
283
  * doesn't linger. Unlike Playwright's `test.fixme()`, which **skips** the test,
195
284
  * `step.fixme` **runs** it — the mandatory reason is there to keep them apart.
285
+ *
286
+ * `step.blocked(name, reason, contract?)` is a pending stub that **can't be
287
+ * authored yet** because the app feature doesn't exist — distinct from a plain
288
+ * `step(name, contract)` stub that's simply awaiting a test. Both report as
289
+ * 'pending' (scenario reads 'incomplete'); the blocked one carries its reason.
290
+ */
291
+ export const step = Object.assign((name, arg2, arg3) => {
292
+ if (typeof arg2 === 'function') {
293
+ return runUnit({ kind: 'step', name, fn: arg2 });
294
+ }
295
+ return runUnit({ kind: 'step', name, contract: arg2, fn: arg3 });
296
+ }, {
297
+ fixme: (name, reason, fn) => runUnit({ kind: 'step', name, reason, fn }),
298
+ blocked: (name, reason, contract) => runUnit({ kind: 'step', name, contract, reason }),
299
+ });
300
+ /**
301
+ * A scenario-level **invariant** — an acceptance property the scenario
302
+ * enforces, independent of the procedural steps. This is the durable "what
303
+ * must be true" that used to live in a scenario's prose Notes; expressing it as
304
+ * a call keeps it in the one source of truth (the test) instead of a separate
305
+ * `.md` that drifts.
306
+ *
307
+ * A failing `invariant` fails the scenario like any hard assertion — it's an
308
+ * acceptance, not a nicety.
309
+ *
310
+ * - `invariant(name, fn)` — enforced now.
311
+ * - `invariant.todo(name, hint?)` — phase-1 stub: states the acceptance but
312
+ * isn't wired yet (status: pending). `opice-author` promotes it to an
313
+ * enforced `invariant(...)` (or an `invariant.fixme(...)` if it can't hold
314
+ * yet) once it knows how to check it.
315
+ * - `invariant.blocked(name, reason)` — a pending acceptance that can't be
316
+ * wired yet because the feature it guards isn't implemented (vs `.todo`,
317
+ * which is merely awaiting authoring). Reports 'pending' with the reason.
318
+ * - `invariant.fixme(name, reason, fn)` — a known-unenforceable acceptance,
319
+ * tolerated like `step.fixme` (e.g. a security property deferred to a
320
+ * ticket). The body runs and is expected to fail; the failure is reported as
321
+ * an amber warning and neither fails the scenario nor the run.
196
322
  */
197
- export const step = Object.assign((name, fn) => runStep(name, fn), {
198
- fixme: (name, reason, fn) => runStep(name, fn, reason),
323
+ export const invariant = Object.assign((name, fn) => runUnit({ kind: 'invariant', name, fn }), {
324
+ todo: (name, hint) => runUnit({ kind: 'invariant', name, contract: hint ? { hint } : undefined }),
325
+ blocked: (name, reason) => runUnit({ kind: 'invariant', name, reason }),
326
+ fixme: (name, reason, fn) => runUnit({ kind: 'invariant', name, reason, fn }),
199
327
  });
200
328
  //# sourceMappingURL=scenario.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scenario.js","sourceRoot":"","sources":["../src/scenario.ts"],"names":[],"mappings":"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"}
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,UAAU,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C;;;;;;GAMG;AACH,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC9C,SAAS,OAAO;IACf,OAAO,OAAO,CAAC,UAAU,CAA8B,CAAA;AACxD,CAAC;AAED,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,wBAAwB,CAAA;AAoChF;;;;GAIG;AACH,SAAS,eAAe;IACvB,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC,KAAK,CAAA;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAA;QACzE,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAA;YAC9C,IAAI,CAAC;gBACJ,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,CAAA;gBAC7C,OAAO,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;YACxC,CAAC;YAAC,MAAM,CAAC;gBACR,OAAO,GAAG,CAAA;YACX,CAAC;QACF,CAAC;IACF,CAAC;IACD,OAAO,SAAS,CAAA;AACjB,CAAC;AAED,IAAI,iBAAiB,GAAkB,IAAI,CAAA;AAC3C,IAAI,oBAAoB,GAAW,CAAC,CAAA;AACpC,IAAI,uBAAuB,GAAG,CAAC,CAAA;AAC/B,IAAI,sBAAsB,GAAG,CAAC,CAAA;AAC9B,6EAA6E;AAC7E,mEAAmE;AACnE,2EAA2E;AAC3E,2DAA2D;AAC3D,IAAI,sBAAsB,GAAG,CAAC,CAAA;AAE9B;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,IAAqB,EAAE,EAAc;IAChE,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9B,yEAAyE;QACzE,MAAM,IAAI,KAAK,CACd,yFAAyF;cACvF,sBAAsB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,mCAAmC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAC7G,CAAA;IACF,CAAC;IACD,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,wFAAwF,CAAC,CAAA;IAC1G,CAAC;IACD,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAA;IAClC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAA;IAEnD,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE;QACxB,SAAS,CAAC,KAAK,IAAI,EAAE;YACpB,oBAAoB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YACjC,uBAAuB,GAAG,CAAC,CAAA;YAC3B,sBAAsB,GAAG,CAAC,CAAA;YAC1B,sBAAsB,GAAG,CAAC,CAAA;YAC1B,IAAI,CAAC;gBACJ,iBAAiB,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC;oBAChD,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,QAAQ;oBACR,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,KAAK,EAAE,IAAI,CAAC,KAAK;iBACjB,CAAC,CAAA;YACH,CAAC;YAAC,MAAM,CAAC;gBACR,iBAAiB,GAAG,IAAI,CAAA;YACzB,CAAC;YACD,IAAI,CAAC;gBACJ,MAAM,IAAI,GAAG,MAAM,UAAU,EAAE,CAAA;gBAC/B,oEAAoE;gBACpE,sEAAsE;gBACtE,8BAA8B;gBAC9B,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAA;gBACnC,IAAI,KAAK;oBAAE,MAAM,KAAK,CAAC,UAAU,EAAE,CAAC,CAAA;gBACpC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,IAAI,cAAc,CAAA;gBACvC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;gBACrD,yEAAyE;gBACzE,wEAAwE;gBACxE,yEAAyE;gBACzE,6CAA6C;gBAC7C,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAA;YACxD,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACZ,sEAAsE;gBACtE,kEAAkE;gBAClE,mEAAmE;gBACnE,gEAAgE;gBAChE,mEAAmE;gBACnE,mEAAmE;gBACnE,gDAAgD;gBAChD,uBAAuB,EAAE,CAAA;gBACzB,IAAI,iBAAiB,EAAE,CAAC;oBACvB,MAAM,KAAK,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;oBACxD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,oBAAoB,CAAA;oBACpD,IAAI,CAAC;wBACJ,MAAM,QAAQ,CAAC,UAAU,CAAC;4BACzB,UAAU,EAAE,iBAAiB;4BAC7B,QAAQ,EAAE,sBAAsB,EAAE;4BAClC,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,gBAAgB;4BACtB,MAAM,EAAE,QAAQ;4BAChB,UAAU;4BACV,KAAK;yBACL,CAAC,CAAA;wBACF,MAAM,QAAQ,CAAC,cAAc,CAAC,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAA;oBAC/F,CAAC;oBAAC,MAAM,CAAC;wBACR,yDAAyD;wBACzD,gDAAgD;oBACjD,CAAC;oBACD,6DAA6D;oBAC7D,iBAAiB,GAAG,IAAI,CAAA;gBACzB,CAAC;gBACD,MAAM,CAAC,CAAA;YACR,CAAC;QACF,CAAC,EAAE,MAAM,CAAC,CAAA;QAEV,QAAQ,CAAC,KAAK,IAAI,EAAE;YACnB,IAAI,CAAC;gBACJ,MAAM,SAAS,EAAE,CAAA;YAClB,CAAC;YAAC,MAAM,CAAC;gBACR,sBAAsB;YACvB,CAAC;YACD,kEAAkE;YAClE,uEAAuE;YACvE,wEAAwE;YACxE,qBAAqB;YACrB,IAAI,sBAAsB,GAAG,CAAC,EAAE,CAAC;gBAChC,OAAO,CAAC,IAAI,CACX,qBAAqB,IAAI,CAAC,IAAI,SAAS,sBAAsB,qBAAqB;sBAChF,6EAA6E,CAC/E,CAAA;YACF,CAAC;YACD,IAAI,iBAAiB,EAAE,CAAC;gBACvB,8DAA8D;gBAC9D,4DAA4D;gBAC5D,+DAA+D;gBAC/D,uCAAuC;gBACvC,IAAI,CAAC;oBACJ,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAA;gBACvB,CAAC;gBAAC,MAAM,CAAC;oBACR,cAAc;gBACf,CAAC;gBACD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,oBAAoB,CAAA;gBACpD,MAAM,MAAM,GAAG,uBAAuB,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAA;gBAChE,IAAI,CAAC;oBACJ,MAAM,QAAQ,CAAC,cAAc,CAAC,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAA;gBACrF,CAAC;gBAAC,MAAM,CAAC;oBACR,cAAc;gBACf,CAAC;YACF,CAAC;YACD,iBAAiB,GAAG,IAAI,CAAA;QACzB,CAAC,EAAE,MAAM,CAAC,CAAA;QAEV,EAAE,EAAE,CAAA;IACL,CAAC,CAAC,CAAA;AACH,CAAC;AAsCD,KAAK,UAAU,OAAO,CAAC,IAAa;IACnC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,uEAAuE;IACvE,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAA;IACzC,6EAA6E;IAC7E,kEAAkE;IAClE,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,CAAA;IAEhE,yEAAyE;IACzE,0EAA0E;IAC1E,yEAAyE;IACzE,yEAAyE;IACzE,6BAA6B;IAC7B,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACd,sBAAsB,EAAE,CAAA;QACxB,IAAI,iBAAiB,EAAE,CAAC;YACvB,KAAK,QAAQ,CAAC,UAAU,CAAC;gBACxB,UAAU,EAAE,iBAAiB;gBAC7B,QAAQ;gBACR,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,MAAM,EAAE,SAAS;gBACjB,UAAU,EAAE,CAAC;gBACb,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM;gBAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;aACnB,CAAC,CAAA;QACH,CAAC;QACD,OAAM;IACP,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACxB,IAAI,MAAM,GAAe,QAAQ,CAAA;IACjC,IAAI,KAAyB,CAAA;IAC7B,IAAI,CAAC;QACJ,MAAM,IAAI,CAAC,EAAE,EAAE,CAAA;QACf,gEAAgE;QAChE,oEAAoE;QACpE,wCAAwC;QACxC,IAAI,KAAK;YAAE,MAAM,GAAG,WAAW,CAAA;IAChC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACZ,KAAK,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;QAClD,IAAI,KAAK,EAAE,CAAC;YACX,sEAAsE;YACtE,uEAAuE;YACvE,mEAAmE;YACnE,iDAAiD;YACjD,MAAM,GAAG,OAAO,CAAA;QACjB,CAAC;aAAM,CAAC;YACP,MAAM,GAAG,QAAQ,CAAA;YACjB,uBAAuB,EAAE,CAAA;YACzB,MAAM,CAAC,CAAA;QACR,CAAC;IACF,CAAC;YAAS,CAAC;QACV,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAA;QACrC,IAAI,cAAkC,CAAA;QACtC,IAAI,CAAC;YACJ,cAAc,GAAG,MAAM,UAAU,EAAE,CAAA;QACpC,CAAC;QAAC,MAAM,CAAC;YACR,6CAA6C;QAC9C,CAAC;QACD,IAAI,iBAAiB,EAAE,CAAC;YACvB,KAAK,QAAQ,CAAC,UAAU,CAAC;gBACxB,UAAU,EAAE,iBAAiB;gBAC7B,QAAQ;gBACR,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,MAAM;gBACN,UAAU;gBACV,KAAK;gBACL,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM;gBAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,cAAc;aACd,CAAC,CAAA;QACH,CAAC;IACF,CAAC;AACF,CAAC;AAwBD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,MAAM,IAAI,GAAwB,MAAM,CAAC,MAAM,CACrD,CAAC,IAAY,EAAE,IAA6B,EAAE,IAAe,EAAiB,EAAE;IAC/E,IAAI,OAAO,IAAI,KAAK,UAAU,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;IACjD,CAAC;IACD,OAAO,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;AACjE,CAAC,EACD;IACC,KAAK,EAAE,CAAC,IAAY,EAAE,MAAc,EAAE,EAAY,EAAiB,EAAE,CACpE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC5C,OAAO,EAAE,CAAC,IAAY,EAAE,MAAc,EAAE,QAAuB,EAAiB,EAAE,CACjF,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;CAClD,CACD,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,SAAS,GAKlB,MAAM,CAAC,MAAM,CAChB,CAAC,IAAY,EAAE,EAAY,EAAiB,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,EACvF;IACC,IAAI,EAAE,CAAC,IAAY,EAAE,IAAa,EAAiB,EAAE,CACpD,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;IAC5E,OAAO,EAAE,CAAC,IAAY,EAAE,MAAc,EAAiB,EAAE,CACxD,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC7C,KAAK,EAAE,CAAC,IAAY,EAAE,MAAc,EAAE,EAAY,EAAiB,EAAE,CACpE,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;CACjD,CACD,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opice/harness",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
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",
package/src/accessible.ts CHANGED
@@ -3,6 +3,8 @@ import { getPage } from './context.js'
3
3
 
4
4
  /** The ARIA role union accepted by Playwright's `getByRole`. */
5
5
  type Role = Parameters<Page['getByRole']>[0]
6
+ /** Playwright's `getByRole` options minus `name` (which is passed positionally). */
7
+ type RoleOptions = Omit<NonNullable<Parameters<Page['getByRole']>[1]>, 'name'>
6
8
 
7
9
  /**
8
10
  * Accessible-name selectors — `byRole` / `byLabel` / `byText`.
@@ -21,10 +23,16 @@ type Role = Parameters<Page['getByRole']>[0]
21
23
 
22
24
  /**
23
25
  * Find an element by ARIA role and (optionally) its accessible name.
24
- * `name` does a substring, case-insensitive match by default.
26
+ * `name` does a substring, case-insensitive match when a string; pass a
27
+ * `RegExp` to match the accessible name by pattern (e.g. a generated id).
28
+ *
29
+ * `options` forwards the rest of Playwright's `getByRole` filters — most useful
30
+ * is `level` to pin a heading (`byRole('heading', 'Title', { level: 2 })`), plus
31
+ * `exact`, `checked`, `pressed`, `expanded`, `disabled`, etc.
25
32
  */
26
- export function byRole(role: Role, name?: string): Locator {
27
- return getPage().getByRole(role, name == null ? undefined : { name })
33
+ export function byRole(role: Role, name?: string | RegExp, options?: RoleOptions): Locator {
34
+ const opts = { ...options, ...(name == null ? {} : { name }) }
35
+ return getPage().getByRole(role, Object.keys(opts).length > 0 ? opts : undefined)
28
36
  }
29
37
 
30
38
  /** Find a form control by its associated `<label>` (or `aria-label`) text. */
package/src/index.ts CHANGED
@@ -6,8 +6,8 @@ export { back, currentPath, currentUrl, forward, open, reload } from './navigati
6
6
 
7
7
  export { getPage, getContext } from './context.js'
8
8
 
9
- export { browserTest, step } from './scenario.js'
10
- export type { BrowserTestOptions } from './scenario.js'
9
+ export { browserTest, invariant, step } from './scenario.js'
10
+ export type { BrowserTestMeta, StepContract } from './scenario.js'
11
11
 
12
12
  export { getReporter, setReporter, configureFromEnv } from './reporter.js'
13
13
  export type { Reporter, ReporterConfig, StepEvent, ScenarioStart, ScenarioFinish } from './reporter.js'
package/src/reporter.ts CHANGED
@@ -39,16 +39,27 @@ export interface StepEvent {
39
39
  scenarioId: string
40
40
  /** Authoring order within the scenario, assigned at step() call time. */
41
41
  sequence: number
42
+ /**
43
+ * 'step' (a procedural step) or 'invariant' (a scenario-level acceptance).
44
+ * The platform may render invariants distinctly; older workers ignore it.
45
+ */
46
+ kind?: 'step' | 'invariant'
42
47
  name: string
43
48
  /**
44
49
  * 'fixme' (a step.fixme that failed, as expected) and 'fixmepass' (a
45
50
  * step.fixme that unexpectedly passed) are tolerated warnings — neither
46
- * fails the scenario.
51
+ * fails the scenario. 'pending' is a phase-1 stub that never ran (no body
52
+ * yet); a scenario carrying one reads as 'incomplete'.
47
53
  */
48
- status: 'passed' | 'failed' | 'fixme' | 'fixmepass'
54
+ status: 'passed' | 'failed' | 'fixme' | 'fixmepass' | 'pending'
49
55
  durationMs: number
50
56
  error?: string
51
- /** Mandatory note from step.fixme — why the failure is tolerated. */
57
+ /**
58
+ * Durable rationale carried from the unit's contract (phase-1 `intent`) —
59
+ * why it exists / what it proves. Surfaced on the dashboard.
60
+ */
61
+ intent?: string
62
+ /** Mandatory note from .fixme — why the failure is tolerated. */
52
63
  reason?: string
53
64
  screenshotPath?: string
54
65
  }
@@ -57,7 +68,12 @@ export interface ScenarioStart {
57
68
  name: string
58
69
  hash?: string
59
70
  testFile?: string
60
- scenarioFile?: string
71
+ /** Requirement / feature id this scenario covers (grouping). */
72
+ feature?: string
73
+ /** Seeds required for the scenario (machine-checkable preconditions). */
74
+ seeds?: string[]
75
+ /** Identities / roles the scenario acts as. */
76
+ roles?: string[]
61
77
  }
62
78
 
63
79
  export interface ScenarioFinish {
@@ -133,7 +149,9 @@ class HttpReporter implements Reporter {
133
149
  name: input.name,
134
150
  hash: input.hash,
135
151
  testFile: input.testFile,
136
- scenarioFile: input.scenarioFile,
152
+ feature: input.feature,
153
+ seeds: input.seeds,
154
+ roles: input.roles,
137
155
  })
138
156
  return response['scenarioId'] as string
139
157
  }
@@ -154,10 +172,12 @@ class HttpReporter implements Reporter {
154
172
  : undefined
155
173
  await this.fetch('POST', `/api/v1/runs/${runId}/scenarios/${event.scenarioId}/steps`, {
156
174
  sequence: event.sequence,
175
+ kind: event.kind,
157
176
  name: event.name,
158
177
  status: event.status,
159
178
  durationMs: event.durationMs,
160
179
  error: event.error,
180
+ intent: event.intent,
161
181
  reason: event.reason,
162
182
  screenshot,
163
183
  })
package/src/scenario.ts CHANGED
@@ -19,17 +19,38 @@ function bunTest(): typeof import('bun:test') {
19
19
 
20
20
  const PLAYGROUND_URL = process.env['PLAYGROUND_URL'] ?? 'http://localhost:15180'
21
21
 
22
- export interface BrowserTestOptions {
23
- /** Hash fragment appended to PLAYGROUND_URL (e.g. 'datagrid'). */
24
- hash?: string
25
- /** Override base URL (defaults to PLAYGROUND_URL env). */
22
+ /**
23
+ * Scenario metadata the **first** argument to `browserTest`.
24
+ *
25
+ * This is the durable, machine-relevant context an opice scenario carries
26
+ * independent of its concrete steps: where it runs, what it presupposes, what
27
+ * requirement it covers. It is written in **phase 1** (planning, `opice-plan`)
28
+ * and preserved through **phase 2** (authoring, `opice-author`) — the scenario
29
+ * file IS the spec, so this metadata never lives in a separate `.md` that can
30
+ * drift from the test.
31
+ *
32
+ * The rule of thumb for what belongs here vs. a code comment: *does anything
33
+ * other than a human read it?* Seeds (a precondition a runner could verify),
34
+ * the feature id (grouping on the dashboard), the acting roles — yes, so they
35
+ * are first-class fields. Background rationale that only a human reads stays a
36
+ * comment next to the relevant step.
37
+ */
38
+ export interface BrowserTestMeta {
39
+ /** Scenario name — becomes the `describe()` title. Required. */
40
+ name: string
41
+ /** Override base URL (defaults to the `PLAYGROUND_URL` env var). */
26
42
  url?: string
43
+ /** Hash fragment appended to the base URL (e.g. `'datagrid'`). */
44
+ hash?: string
45
+ /** Feature / requirement id this scenario covers (e.g. `'F-SML-03a'`). */
46
+ feature?: string
27
47
  /**
28
- * Path to the human-readable `*.scenario.md` this test was authored from.
29
- * Reported to the platform so the re-eval workflow can find the source.
30
- * If omitted, defaults to the test file path with `.test.ts` → `.scenario.md`.
48
+ * Seeds that must be loaded for this scenario to run — machine-checkable
49
+ * preconditions, not prose. e.g. `['initial-data', 'crm-master-data']`.
31
50
  */
32
- scenarioFile?: string
51
+ seeds?: string[]
52
+ /** Identities / roles the scenario acts as, e.g. `['crmOperator']`. */
53
+ roles?: string[]
33
54
  }
34
55
 
35
56
  /**
@@ -55,14 +76,10 @@ function captureTestFile(): string | undefined {
55
76
  return undefined
56
77
  }
57
78
 
58
- function defaultScenarioFile(testFile: string | undefined): string | undefined {
59
- if (!testFile) return undefined
60
- return testFile.replace(/\.test\.[tj]sx?$/, '.scenario.md')
61
- }
62
-
63
79
  let currentScenarioId: string | null = null
64
80
  let currentScenarioStart: number = 0
65
81
  let currentScenarioFailures = 0
82
+ let currentScenarioPending = 0
66
83
  // Monotonic per-scenario step counter. Assigned synchronously at each step()
67
84
  // call so order reflects authoring order — step records are POSTed
68
85
  // fire-and-forget and would otherwise be sequenced by arrival order at the
@@ -72,41 +89,93 @@ let currentScenarioStepSeq = 0
72
89
  /**
73
90
  * Register a top-level browser test scenario.
74
91
  *
75
- * Each `browserTest(name, fn)` launches its own isolated Playwright browser +
92
+ * Each `browserTest(meta, fn)` launches its own isolated Playwright browser +
76
93
  * context + page, navigates to the playground URL, runs the given `fn` (which
77
94
  * typically contains nested `describe`/`test` blocks), and tears the browser
78
95
  * down in `afterAll`.
96
+ *
97
+ * Metadata is the **first** argument (`{ name, url, hash, feature, seeds,
98
+ * roles }`); `name` is required.
79
99
  */
80
- export function browserTest(name: string, fn: () => void, options: BrowserTestOptions | string = {}): void {
81
- const opts: BrowserTestOptions = typeof options === 'string' ? { hash: options } : options
100
+ export function browserTest(meta: BrowserTestMeta, fn: () => void): void {
101
+ if (typeof meta === 'string') {
102
+ // Migration aid: the old signature was `browserTest(name, fn, options)`.
103
+ throw new Error(
104
+ 'opice: browserTest now takes metadata first — browserTest({ name, url, hash, … }, fn). '
105
+ + `Got a string name (${JSON.stringify(meta)}); wrap it: browserTest({ name: ${JSON.stringify(meta)} }, fn).`,
106
+ )
107
+ }
108
+ if (!meta?.name) {
109
+ throw new Error('opice: browserTest requires a `name` in its metadata — browserTest({ name: "…" }, fn).')
110
+ }
82
111
  const reporter = getReporter()
83
112
  const testFile = captureTestFile()
84
- const scenarioFile = opts.scenarioFile ?? defaultScenarioFile(testFile)
85
113
  const { describe, beforeAll, afterAll } = bunTest()
86
114
 
87
- describe(name, () => {
115
+ describe(meta.name, () => {
88
116
  beforeAll(async () => {
89
117
  currentScenarioStart = Date.now()
90
118
  currentScenarioFailures = 0
119
+ currentScenarioPending = 0
91
120
  currentScenarioStepSeq = 0
92
121
  try {
93
- currentScenarioId = await reporter.startScenario({ name, hash: opts.hash, testFile, scenarioFile })
122
+ currentScenarioId = await reporter.startScenario({
123
+ name: meta.name,
124
+ hash: meta.hash,
125
+ testFile,
126
+ feature: meta.feature,
127
+ seeds: meta.seeds,
128
+ roles: meta.roles,
129
+ })
94
130
  } catch {
95
131
  currentScenarioId = null
96
132
  }
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())
103
- const base = opts.url ?? PLAYGROUND_URL
104
- const url = opts.hash ? `${base}#${opts.hash}` : base
105
- // `domcontentloaded`, not the default `load`: an SPA paints after its JS
133
+ try {
134
+ const page = await launchPage()
135
+ // Repo-level context setup (browser-setup.ts) runs before the first
136
+ // navigation, so an addInitScript it registers fires before the app's
137
+ // own scripts on first paint.
138
+ const setup = await loadUserSetup()
139
+ if (setup) await setup(getContext())
140
+ const base = meta.url ?? PLAYGROUND_URL
141
+ const url = meta.hash ? `${base}#${meta.hash}` : base
142
+ // `domcontentloaded`, not the default `load`: an SPA paints after its JS
106
143
  // runs and may hold `load` on a slow chunk or long-lived connection, so
107
144
  // waiting for `load` flakily times out under CI contention. Readiness is
108
145
  // handled by the test's retrying assertions.
109
146
  await page.goto(url, { waitUntil: 'domcontentloaded' })
147
+ } catch (e) {
148
+ // Setup failed before any step ran (e.g. a wrong playground URL whose
149
+ // goto is refused). bun:test does NOT run afterAll when beforeAll
150
+ // throws, so the scenario we already started above would otherwise
151
+ // sit on the dashboard as 'running' forever (see reporter.ts) —
152
+ // invisible as a failure even though CI is red. Record a synthetic
153
+ // failed step so the dashboard shows *why*, finish the scenario as
154
+ // failed, then re-throw so the run still fails.
155
+ currentScenarioFailures++
156
+ if (currentScenarioId) {
157
+ const error = e instanceof Error ? e.message : String(e)
158
+ const durationMs = Date.now() - currentScenarioStart
159
+ try {
160
+ await reporter.recordStep({
161
+ scenarioId: currentScenarioId,
162
+ sequence: currentScenarioStepSeq++,
163
+ kind: 'step',
164
+ name: 'scenario setup',
165
+ status: 'failed',
166
+ durationMs,
167
+ error,
168
+ })
169
+ await reporter.finishScenario({ scenarioId: currentScenarioId, status: 'failed', durationMs })
170
+ } catch {
171
+ // best-effort: reporting the failure must never mask the
172
+ // original setup error we're about to re-throw.
173
+ }
174
+ // Null it so afterAll (should it run) doesn't double-finish.
175
+ currentScenarioId = null
176
+ }
177
+ throw e
178
+ }
110
179
  }, 30_000)
111
180
 
112
181
  afterAll(async () => {
@@ -115,6 +184,16 @@ export function browserTest(name: string, fn: () => void, options: BrowserTestOp
115
184
  } catch {
116
185
  // ignore close errors
117
186
  }
187
+ // A scenario still carrying unfilled (pending) steps is a phase-1
188
+ // skeleton that was run before authoring. It's not a failure, but it's
189
+ // not done either — make it loud so a half-authored test isn't mistaken
190
+ // for a passing one.
191
+ if (currentScenarioPending > 0) {
192
+ console.warn(
193
+ `[opice] scenario "${meta.name}" has ${currentScenarioPending} pending step(s) — `
194
+ + 'authored skeleton, not yet filled in by opice-author. The body did NOT run.',
195
+ )
196
+ }
118
197
  if (currentScenarioId) {
119
198
  // Drain pending step records (incl. their screenshot uploads)
120
199
  // before marking the scenario done. step() fires recordStep
@@ -140,19 +219,78 @@ export function browserTest(name: string, fn: () => void, options: BrowserTestOp
140
219
  })
141
220
  }
142
221
 
143
- type StepStatus = 'passed' | 'failed' | 'fixme' | 'fixmepass'
222
+ type StepStatus = 'passed' | 'failed' | 'fixme' | 'fixmepass' | 'pending'
223
+ type StepKind = 'step' | 'invariant'
144
224
 
145
- async function runStep(name: string, fn: () => void | Promise<void>, fixmeReason?: string): Promise<void> {
225
+ /**
226
+ * The durable contract of a step or invariant, separate from its mechanics.
227
+ *
228
+ * `intent` is written in **phase 1** and survives **verbatim** into the
229
+ * authored test — it's the "why this exists / what it proves", the independent
230
+ * statement of intent that the concrete body is checked against. `hint` is
231
+ * phase-1 scaffolding *for the authoring agent* ("what to actually do here");
232
+ * it is consumed when the step is authored and dropped once a body exists.
233
+ */
234
+ export interface StepContract {
235
+ /** Durable rationale: why this step/invariant exists, what it proves. */
236
+ intent?: string
237
+ /**
238
+ * Phase-1 instruction to the authoring agent — what to do on the page here.
239
+ * Ephemeral: drop it once the body is written.
240
+ */
241
+ hint?: string
242
+ }
243
+
244
+ interface RunUnit {
245
+ kind: StepKind
246
+ name: string
247
+ contract?: StepContract
248
+ /** Present once authored. Absent ⇒ a pending (phase-1) stub. */
249
+ fn?: () => void | Promise<void>
250
+ /**
251
+ * A human note. With a body (`.fixme`): why a tolerated failure is allowed.
252
+ * Without a body (`.blocked`): why the stub can't be authored yet (the app
253
+ * feature isn't implemented). A plain pending stub has no reason.
254
+ */
255
+ reason?: string
256
+ }
257
+
258
+ async function runUnit(unit: RunUnit): Promise<void> {
146
259
  const reporter = getReporter()
147
260
  // Capture order at call time, before the fire-and-forget record below.
148
261
  const sequence = currentScenarioStepSeq++
262
+ // A reason *with* a body is a .fixme (tolerated failure). A reason *without*
263
+ // a body is .blocked (a pending stub that can't be authored yet).
264
+ const fixme = unit.reason !== undefined && unit.fn !== undefined
265
+
266
+ // Phase-1 stub: no body to run. Report it as 'pending' (so the dashboard
267
+ // shows the skeleton — a scenario carrying one reads as 'incomplete') and
268
+ // count it so afterAll can warn. A `reason` here marks it 'blocked' (the
269
+ // feature isn't built); no reason is a plain todo awaiting authoring. No
270
+ // screenshot, zero duration.
271
+ if (!unit.fn) {
272
+ currentScenarioPending++
273
+ if (currentScenarioId) {
274
+ void reporter.recordStep({
275
+ scenarioId: currentScenarioId,
276
+ sequence,
277
+ kind: unit.kind,
278
+ name: unit.name,
279
+ status: 'pending',
280
+ durationMs: 0,
281
+ intent: unit.contract?.intent,
282
+ reason: unit.reason,
283
+ })
284
+ }
285
+ return
286
+ }
287
+
149
288
  const start = Date.now()
150
- const fixme = fixmeReason !== undefined
151
289
  let status: StepStatus = 'passed'
152
290
  let error: string | undefined
153
291
  try {
154
- await fn()
155
- // A fixme step that *passes* is a stale marker: surface it as an
292
+ await unit.fn()
293
+ // A fixme unit that *passes* is a stale marker: surface it as a
156
294
  // 'fixmepass' warning so the author knows they can drop the marker,
157
295
  // rather than letting it pass silently.
158
296
  if (fixme) status = 'fixmepass'
@@ -161,7 +299,7 @@ async function runStep(name: string, fn: () => void | Promise<void>, fixmeReason
161
299
  if (fixme) {
162
300
  // Known / tolerated failure. Record it as 'fixme', but DON'T count it
163
301
  // toward scenario failures and DON'T re-throw — that's the whole point
164
- // of step.fixme: the scenario (and the CI run) stay green, the failure
302
+ // of .fixme: the scenario (and the CI run) stay green, the failure
165
303
  // surfaces as an amber warning on the dashboard.
166
304
  status = 'fixme'
167
305
  } else {
@@ -181,17 +319,41 @@ async function runStep(name: string, fn: () => void | Promise<void>, fixmeReason
181
319
  void reporter.recordStep({
182
320
  scenarioId: currentScenarioId,
183
321
  sequence,
184
- name,
322
+ kind: unit.kind,
323
+ name: unit.name,
185
324
  status,
186
325
  durationMs,
187
326
  error,
188
- reason: fixmeReason,
327
+ intent: unit.contract?.intent,
328
+ reason: unit.reason,
189
329
  screenshotPath,
190
330
  })
191
331
  }
192
332
  }
193
333
  }
194
334
 
335
+ type StepBody = () => void | Promise<void>
336
+
337
+ interface StepFn {
338
+ /** Executable step. */
339
+ (name: string, fn: StepBody): Promise<void>
340
+ /** Phase-1 stub: a step with a contract but no body yet (status: pending). */
341
+ (name: string, contract: StepContract): Promise<void>
342
+ /** Authored step that keeps its durable contract. */
343
+ (name: string, contract: StepContract, fn: StepBody): Promise<void>
344
+ }
345
+
346
+ interface StepExtras {
347
+ fixme: (name: string, reason: string, fn: StepBody) => Promise<void>
348
+ /**
349
+ * A **blocked** pending stub: the step can't be authored yet because the app
350
+ * feature it covers isn't implemented. Reports as 'pending' with the reason
351
+ * attached (the dashboard shows it as blocked, distinct from a plain stub
352
+ * that's merely awaiting a test). `reason` is mandatory — say what's missing.
353
+ */
354
+ blocked: (name: string, reason: string, contract?: StepContract) => Promise<void>
355
+ }
356
+
195
357
  /**
196
358
  * A reportable step inside a scenario. Captures duration + screenshot on
197
359
  * finish, forwards to the active reporter (no-op unless configured via env).
@@ -199,6 +361,14 @@ async function runStep(name: string, fn: () => void | Promise<void>, fixmeReason
199
361
  * The body may be sync or async; `step` always returns a promise, so call it
200
362
  * with `await step('…', async () => { … })`.
201
363
  *
364
+ * Three forms:
365
+ * - `step(name, fn)` — executable step (the common case).
366
+ * - `step(name, { intent, hint })` — a **pending** phase-1 stub: declares the
367
+ * step's intent and what to do, but has no body yet. It does not run and
368
+ * reports as `pending`; `opice-author` fills in the body.
369
+ * - `step(name, { intent }, fn)` — an authored step that keeps its durable
370
+ * `intent` (preserved verbatim from phase 1) alongside the body.
371
+ *
202
372
  * `step.fixme(name, reason, fn)` marks a **known, tolerated failure**: the body
203
373
  * still runs, but a failure inside it does NOT fail the scenario or the CI run —
204
374
  * it's reported as an amber warning instead. The `reason` is mandatory (use it
@@ -206,11 +376,63 @@ async function runStep(name: string, fn: () => void | Promise<void>, fixmeReason
206
376
  * step unexpectedly *passes*, it's flagged too ('fixmepass') so a stale marker
207
377
  * doesn't linger. Unlike Playwright's `test.fixme()`, which **skips** the test,
208
378
  * `step.fixme` **runs** it — the mandatory reason is there to keep them apart.
379
+ *
380
+ * `step.blocked(name, reason, contract?)` is a pending stub that **can't be
381
+ * authored yet** because the app feature doesn't exist — distinct from a plain
382
+ * `step(name, contract)` stub that's simply awaiting a test. Both report as
383
+ * 'pending' (scenario reads 'incomplete'); the blocked one carries its reason.
384
+ */
385
+ export const step: StepFn & StepExtras = Object.assign(
386
+ (name: string, arg2: StepBody | StepContract, arg3?: StepBody): Promise<void> => {
387
+ if (typeof arg2 === 'function') {
388
+ return runUnit({ kind: 'step', name, fn: arg2 })
389
+ }
390
+ return runUnit({ kind: 'step', name, contract: arg2, fn: arg3 })
391
+ },
392
+ {
393
+ fixme: (name: string, reason: string, fn: StepBody): Promise<void> =>
394
+ runUnit({ kind: 'step', name, reason, fn }),
395
+ blocked: (name: string, reason: string, contract?: StepContract): Promise<void> =>
396
+ runUnit({ kind: 'step', name, contract, reason }),
397
+ },
398
+ )
399
+
400
+ /**
401
+ * A scenario-level **invariant** — an acceptance property the scenario
402
+ * enforces, independent of the procedural steps. This is the durable "what
403
+ * must be true" that used to live in a scenario's prose Notes; expressing it as
404
+ * a call keeps it in the one source of truth (the test) instead of a separate
405
+ * `.md` that drifts.
406
+ *
407
+ * A failing `invariant` fails the scenario like any hard assertion — it's an
408
+ * acceptance, not a nicety.
409
+ *
410
+ * - `invariant(name, fn)` — enforced now.
411
+ * - `invariant.todo(name, hint?)` — phase-1 stub: states the acceptance but
412
+ * isn't wired yet (status: pending). `opice-author` promotes it to an
413
+ * enforced `invariant(...)` (or an `invariant.fixme(...)` if it can't hold
414
+ * yet) once it knows how to check it.
415
+ * - `invariant.blocked(name, reason)` — a pending acceptance that can't be
416
+ * wired yet because the feature it guards isn't implemented (vs `.todo`,
417
+ * which is merely awaiting authoring). Reports 'pending' with the reason.
418
+ * - `invariant.fixme(name, reason, fn)` — a known-unenforceable acceptance,
419
+ * tolerated like `step.fixme` (e.g. a security property deferred to a
420
+ * ticket). The body runs and is expected to fail; the failure is reported as
421
+ * an amber warning and neither fails the scenario nor the run.
209
422
  */
210
- export const step = Object.assign(
211
- (name: string, fn: () => void | Promise<void>): Promise<void> => runStep(name, fn),
423
+ export const invariant: {
424
+ (name: string, fn: StepBody): Promise<void>
425
+ todo: (name: string, hint?: string) => Promise<void>
426
+ blocked: (name: string, reason: string) => Promise<void>
427
+ fixme: (name: string, reason: string, fn: StepBody) => Promise<void>
428
+ } = Object.assign(
429
+ (name: string, fn: StepBody): Promise<void> => runUnit({ kind: 'invariant', name, fn }),
212
430
  {
213
- fixme: (name: string, reason: string, fn: () => void | Promise<void>): Promise<void> =>
214
- runStep(name, fn, reason),
431
+ todo: (name: string, hint?: string): Promise<void> =>
432
+ runUnit({ kind: 'invariant', name, contract: hint ? { hint } : undefined }),
433
+ blocked: (name: string, reason: string): Promise<void> =>
434
+ runUnit({ kind: 'invariant', name, reason }),
435
+ fixme: (name: string, reason: string, fn: StepBody): Promise<void> =>
436
+ runUnit({ kind: 'invariant', name, reason, fn }),
215
437
  },
216
438
  )