sentienceapi 0.93.0 → 0.94.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/asserts/expect.d.ts +159 -0
  2. package/dist/asserts/expect.d.ts.map +1 -0
  3. package/dist/asserts/expect.js +547 -0
  4. package/dist/asserts/expect.js.map +1 -0
  5. package/dist/asserts/index.d.ts +58 -0
  6. package/dist/asserts/index.d.ts.map +1 -0
  7. package/dist/asserts/index.js +70 -0
  8. package/dist/asserts/index.js.map +1 -0
  9. package/dist/asserts/query.d.ts +199 -0
  10. package/dist/asserts/query.d.ts.map +1 -0
  11. package/dist/asserts/query.js +288 -0
  12. package/dist/asserts/query.js.map +1 -0
  13. package/dist/backends/actions.d.ts +118 -0
  14. package/dist/backends/actions.d.ts.map +1 -0
  15. package/dist/backends/actions.js +262 -0
  16. package/dist/backends/actions.js.map +1 -0
  17. package/dist/backends/browser-use-adapter.d.ts +131 -0
  18. package/dist/backends/browser-use-adapter.d.ts.map +1 -0
  19. package/dist/backends/browser-use-adapter.js +219 -0
  20. package/dist/backends/browser-use-adapter.js.map +1 -0
  21. package/dist/backends/cdp-backend.d.ts +66 -0
  22. package/dist/backends/cdp-backend.d.ts.map +1 -0
  23. package/dist/backends/cdp-backend.js +273 -0
  24. package/dist/backends/cdp-backend.js.map +1 -0
  25. package/dist/backends/index.d.ts +80 -0
  26. package/dist/backends/index.d.ts.map +1 -0
  27. package/dist/backends/index.js +100 -0
  28. package/dist/backends/index.js.map +1 -0
  29. package/dist/backends/protocol.d.ts +156 -0
  30. package/dist/backends/protocol.d.ts.map +1 -0
  31. package/dist/backends/protocol.js +16 -0
  32. package/dist/backends/protocol.js.map +1 -0
  33. package/dist/backends/sentience-context.d.ts +136 -0
  34. package/dist/backends/sentience-context.d.ts.map +1 -0
  35. package/dist/backends/sentience-context.js +354 -0
  36. package/dist/backends/sentience-context.js.map +1 -0
  37. package/dist/backends/snapshot.d.ts +180 -0
  38. package/dist/backends/snapshot.d.ts.map +1 -0
  39. package/dist/backends/snapshot.js +308 -0
  40. package/dist/backends/snapshot.js.map +1 -0
  41. package/dist/index.d.ts +1 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +25 -1
  44. package/dist/index.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/extension/release.json +1 -1
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Expectation builder for assertion DSL.
3
+ *
4
+ * This module provides the expect() builder that creates fluent assertions
5
+ * which compile to existing Predicate objects.
6
+ *
7
+ * Key classes:
8
+ * - ExpectBuilder: Fluent builder for element-based assertions
9
+ * - EventuallyConfig: Configuration for .eventually() retry logic
10
+ *
11
+ * The expect() function is the main entry point. It returns a builder that
12
+ * can be chained with matchers:
13
+ * expect(E({ role: "button" })).toExist()
14
+ * expect(E({ textContains: "Error" })).notToExist()
15
+ * expect.textPresent("Welcome")
16
+ *
17
+ * All builders compile to Predicate functions compatible with AgentRuntime.assert().
18
+ */
19
+ import { Predicate, AssertOutcome, AssertContext } from '../verification';
20
+ import { ElementQuery, MultiQuery, MultiTextPredicate } from './query';
21
+ /**
22
+ * Configuration for .eventually() retry logic.
23
+ */
24
+ export interface EventuallyConfig {
25
+ /** Max time to wait (milliseconds, default 10000) */
26
+ timeout?: number;
27
+ /** Interval between retries (milliseconds, default 200) */
28
+ poll?: number;
29
+ /** Max number of retry attempts (default 3) */
30
+ maxRetries?: number;
31
+ }
32
+ /**
33
+ * Fluent builder for element-based assertions.
34
+ *
35
+ * Created by expect(E(...)) or expect(inDominantList().nth(k)).
36
+ *
37
+ * Methods return Predicate functions that can be passed to runtime.assert().
38
+ *
39
+ * @example
40
+ * expect(E({ role: "button" })).toExist()
41
+ * expect(E({ textContains: "Error" })).notToExist()
42
+ * expect(E({ role: "link" })).toBeVisible()
43
+ */
44
+ export declare class ExpectBuilder {
45
+ private _query;
46
+ constructor(query: ElementQuery | MultiQuery | MultiTextPredicate);
47
+ /**
48
+ * Assert that at least one element matches the query.
49
+ *
50
+ * @returns Predicate function for use with runtime.assert()
51
+ *
52
+ * @example
53
+ * await runtime.assert(
54
+ * expect(E({ role: "button", textContains: "Save" })).toExist(),
55
+ * "save_button_exists"
56
+ * );
57
+ */
58
+ toExist(): Predicate;
59
+ /**
60
+ * Assert that NO elements match the query.
61
+ *
62
+ * Useful for asserting absence of error messages, loading indicators, etc.
63
+ *
64
+ * @returns Predicate function for use with runtime.assert()
65
+ *
66
+ * @example
67
+ * await runtime.assert(
68
+ * expect(E({ textContains: "Error" })).notToExist(),
69
+ * "no_error_message"
70
+ * );
71
+ */
72
+ notToExist(): Predicate;
73
+ /**
74
+ * Assert that element exists AND is visible (in_viewport=true, is_occluded=false).
75
+ *
76
+ * @returns Predicate function for use with runtime.assert()
77
+ *
78
+ * @example
79
+ * await runtime.assert(
80
+ * expect(E({ textContains: "Checkout" })).toBeVisible(),
81
+ * "checkout_button_visible"
82
+ * );
83
+ */
84
+ toBeVisible(): Predicate;
85
+ /**
86
+ * Assert that element's text contains the specified substring.
87
+ *
88
+ * @param text - Substring to search for (case-insensitive)
89
+ * @returns Predicate function for use with runtime.assert()
90
+ *
91
+ * @example
92
+ * await runtime.assert(
93
+ * expect(inDominantList().nth(0)).toHaveTextContains("Show HN"),
94
+ * "first_item_is_show_hn"
95
+ * );
96
+ */
97
+ toHaveTextContains(text: string): Predicate;
98
+ }
99
+ /**
100
+ * Main entry point for the assertion DSL.
101
+ *
102
+ * Use as a function to create element-based assertions:
103
+ * expect(E({ role: "button" })).toExist()
104
+ *
105
+ * Use static methods for global assertions:
106
+ * expect.textPresent("Welcome")
107
+ * expect.noText("Error")
108
+ */
109
+ export declare const expect: ((query: ElementQuery | MultiQuery | MultiTextPredicate) => ExpectBuilder) & {
110
+ textPresent: (text: string) => Predicate;
111
+ noText: (text: string) => Predicate;
112
+ };
113
+ /**
114
+ * Wrapper that adds retry logic to a predicate.
115
+ *
116
+ * Created by withEventually(). Provides an async evaluate() method
117
+ * that retries the predicate with fresh snapshots.
118
+ *
119
+ * Note: TypeScript uses milliseconds for timeout/poll.
120
+ */
121
+ export declare class EventuallyWrapper {
122
+ private _predicate;
123
+ private _config;
124
+ constructor(predicate: Predicate, config?: EventuallyConfig);
125
+ /**
126
+ * Evaluate predicate with retry logic.
127
+ *
128
+ * @param ctx - Initial assertion context
129
+ * @param snapshotFn - Async function to take fresh snapshots
130
+ * @returns Promise resolving to AssertOutcome
131
+ */
132
+ evaluate(ctx: AssertContext, snapshotFn: () => Promise<AssertContext['snapshot']>): Promise<AssertOutcome>;
133
+ private sleep;
134
+ /** Get the configured timeout in milliseconds */
135
+ get timeout(): number;
136
+ /** Get the configured poll interval in milliseconds */
137
+ get poll(): number;
138
+ /** Get the configured max retries */
139
+ get maxRetries(): number;
140
+ }
141
+ /**
142
+ * Wrap a predicate with retry logic.
143
+ *
144
+ * This is the TypeScript API for .eventually(). Returns a wrapper
145
+ * that provides an async evaluate() method for use with the runtime.
146
+ *
147
+ * @param predicate - Predicate to wrap
148
+ * @param config - Retry configuration (timeout/poll in milliseconds)
149
+ * @returns EventuallyWrapper with async evaluate() method
150
+ *
151
+ * @example
152
+ * const wrapper = withEventually(
153
+ * expect(E({ role: "button" })).toExist(),
154
+ * { timeout: 5000, maxRetries: 10 }
155
+ * );
156
+ * const result = await wrapper.evaluate(ctx, runtime.snapshot);
157
+ */
158
+ export declare function withEventually(predicate: Predicate, config?: EventuallyConfig): EventuallyWrapper;
159
+ //# sourceMappingURL=expect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expect.d.ts","sourceRoot":"","sources":["../../src/asserts/expect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAOvE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAoCD;;;;;;;;;;;GAWG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAiD;gBAEnD,KAAK,EAAE,YAAY,GAAG,UAAU,GAAG,kBAAkB;IAIjE;;;;;;;;;;OAUG;IACH,OAAO,IAAI,SAAS;IA+BpB;;;;;;;;;;;;OAYG;IACH,UAAU,IAAI,SAAS;IAiCvB;;;;;;;;;;OAUG;IACH,WAAW,IAAI,SAAS;IAgDxB;;;;;;;;;;;OAWG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS;CA8C5C;AAqID;;;;;;;;;GASG;AACH,eAAO,MAAM,MAAM,WACT,YAAY,GAAG,UAAU,GAAG,kBAAkB;wBAEhC,MAAM;mBACX,MAAM;CAExB,CAAC;AAEF;;;;;;;GAOG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,OAAO,CAA6B;gBAEhC,SAAS,EAAE,SAAS,EAAE,MAAM,GAAE,gBAAqB;IAS/D;;;;;;OAMG;IACG,QAAQ,CACZ,GAAG,EAAE,aAAa,EAClB,UAAU,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,GACnD,OAAO,CAAC,aAAa,CAAC;IA8EzB,OAAO,CAAC,KAAK;IAIb,iDAAiD;IACjD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,uDAAuD;IACvD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,qCAAqC;IACrC,IAAI,UAAU,IAAI,MAAM,CAEvB;CACF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,gBAAgB,GAAG,iBAAiB,CAEjG"}
@@ -0,0 +1,547 @@
1
+ "use strict";
2
+ /**
3
+ * Expectation builder for assertion DSL.
4
+ *
5
+ * This module provides the expect() builder that creates fluent assertions
6
+ * which compile to existing Predicate objects.
7
+ *
8
+ * Key classes:
9
+ * - ExpectBuilder: Fluent builder for element-based assertions
10
+ * - EventuallyConfig: Configuration for .eventually() retry logic
11
+ *
12
+ * The expect() function is the main entry point. It returns a builder that
13
+ * can be chained with matchers:
14
+ * expect(E({ role: "button" })).toExist()
15
+ * expect(E({ textContains: "Error" })).notToExist()
16
+ * expect.textPresent("Welcome")
17
+ *
18
+ * All builders compile to Predicate functions compatible with AgentRuntime.assert().
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.EventuallyWrapper = exports.expect = exports.ExpectBuilder = void 0;
22
+ exports.withEventually = withEventually;
23
+ const query_1 = require("./query");
24
+ // Default values for .eventually()
25
+ const DEFAULT_TIMEOUT = 10000; // milliseconds
26
+ const DEFAULT_POLL = 200; // milliseconds
27
+ const DEFAULT_MAX_RETRIES = 3;
28
+ /**
29
+ * Convert query to a serializable object for debugging.
30
+ */
31
+ function queryToDict(query) {
32
+ if (query instanceof query_1.ElementQuery) {
33
+ const result = {};
34
+ if (query.role)
35
+ result.role = query.role;
36
+ if (query.name)
37
+ result.name = query.name;
38
+ if (query.text)
39
+ result.text = query.text;
40
+ if (query.textContains)
41
+ result.textContains = query.textContains;
42
+ if (query.hrefContains)
43
+ result.hrefContains = query.hrefContains;
44
+ if (query.inViewport !== undefined)
45
+ result.inViewport = query.inViewport;
46
+ if (query.occluded !== undefined)
47
+ result.occluded = query.occluded;
48
+ if (query.group)
49
+ result.group = query.group;
50
+ if (query.inDominantGroup !== undefined)
51
+ result.inDominantGroup = query.inDominantGroup;
52
+ if (query._groupIndex !== undefined)
53
+ result.groupIndex = query._groupIndex;
54
+ if (query._fromDominantList)
55
+ result.fromDominantList = true;
56
+ return result;
57
+ }
58
+ else if (query instanceof query_1.MultiQuery) {
59
+ return { type: 'multi', limit: query.limit };
60
+ }
61
+ else if (typeof query === 'object' &&
62
+ query !== null &&
63
+ 'checkType' in query &&
64
+ 'text' in query &&
65
+ 'multiQuery' in query) {
66
+ return { type: 'multi_text', text: query.text, checkType: query.checkType };
67
+ }
68
+ return { type: String(typeof query) };
69
+ }
70
+ /**
71
+ * Fluent builder for element-based assertions.
72
+ *
73
+ * Created by expect(E(...)) or expect(inDominantList().nth(k)).
74
+ *
75
+ * Methods return Predicate functions that can be passed to runtime.assert().
76
+ *
77
+ * @example
78
+ * expect(E({ role: "button" })).toExist()
79
+ * expect(E({ textContains: "Error" })).notToExist()
80
+ * expect(E({ role: "link" })).toBeVisible()
81
+ */
82
+ class ExpectBuilder {
83
+ constructor(query) {
84
+ this._query = query;
85
+ }
86
+ /**
87
+ * Assert that at least one element matches the query.
88
+ *
89
+ * @returns Predicate function for use with runtime.assert()
90
+ *
91
+ * @example
92
+ * await runtime.assert(
93
+ * expect(E({ role: "button", textContains: "Save" })).toExist(),
94
+ * "save_button_exists"
95
+ * );
96
+ */
97
+ toExist() {
98
+ const query = this._query;
99
+ return (ctx) => {
100
+ const snap = ctx.snapshot;
101
+ if (!snap) {
102
+ return {
103
+ passed: false,
104
+ reason: 'no snapshot available',
105
+ details: { query: queryToDict(query) },
106
+ };
107
+ }
108
+ if (query instanceof query_1.ElementQuery) {
109
+ const matches = query.findAll(snap);
110
+ const ok = matches.length > 0;
111
+ return {
112
+ passed: ok,
113
+ reason: ok ? '' : `no elements matched query: ${JSON.stringify(queryToDict(query))}`,
114
+ details: { query: queryToDict(query), matched: matches.length },
115
+ };
116
+ }
117
+ return {
118
+ passed: false,
119
+ reason: 'toExist() requires ElementQuery',
120
+ details: {},
121
+ };
122
+ };
123
+ }
124
+ /**
125
+ * Assert that NO elements match the query.
126
+ *
127
+ * Useful for asserting absence of error messages, loading indicators, etc.
128
+ *
129
+ * @returns Predicate function for use with runtime.assert()
130
+ *
131
+ * @example
132
+ * await runtime.assert(
133
+ * expect(E({ textContains: "Error" })).notToExist(),
134
+ * "no_error_message"
135
+ * );
136
+ */
137
+ notToExist() {
138
+ const query = this._query;
139
+ return (ctx) => {
140
+ const snap = ctx.snapshot;
141
+ if (!snap) {
142
+ return {
143
+ passed: false,
144
+ reason: 'no snapshot available',
145
+ details: { query: queryToDict(query) },
146
+ };
147
+ }
148
+ if (query instanceof query_1.ElementQuery) {
149
+ const matches = query.findAll(snap);
150
+ const ok = matches.length === 0;
151
+ return {
152
+ passed: ok,
153
+ reason: ok
154
+ ? ''
155
+ : `found ${matches.length} elements matching: ${JSON.stringify(queryToDict(query))}`,
156
+ details: { query: queryToDict(query), matched: matches.length },
157
+ };
158
+ }
159
+ return {
160
+ passed: false,
161
+ reason: 'notToExist() requires ElementQuery',
162
+ details: {},
163
+ };
164
+ };
165
+ }
166
+ /**
167
+ * Assert that element exists AND is visible (in_viewport=true, is_occluded=false).
168
+ *
169
+ * @returns Predicate function for use with runtime.assert()
170
+ *
171
+ * @example
172
+ * await runtime.assert(
173
+ * expect(E({ textContains: "Checkout" })).toBeVisible(),
174
+ * "checkout_button_visible"
175
+ * );
176
+ */
177
+ toBeVisible() {
178
+ const query = this._query;
179
+ return (ctx) => {
180
+ const snap = ctx.snapshot;
181
+ if (!snap) {
182
+ return {
183
+ passed: false,
184
+ reason: 'no snapshot available',
185
+ details: { query: queryToDict(query) },
186
+ };
187
+ }
188
+ if (query instanceof query_1.ElementQuery) {
189
+ const matches = query.findAll(snap);
190
+ if (matches.length === 0) {
191
+ return {
192
+ passed: false,
193
+ reason: `no elements matched query: ${JSON.stringify(queryToDict(query))}`,
194
+ details: { query: queryToDict(query), matched: 0 },
195
+ };
196
+ }
197
+ // Check visibility of first match
198
+ const el = matches[0];
199
+ const isVisible = el.in_viewport && !el.is_occluded;
200
+ return {
201
+ passed: isVisible,
202
+ reason: isVisible
203
+ ? ''
204
+ : `element found but not visible (in_viewport=${el.in_viewport}, is_occluded=${el.is_occluded})`,
205
+ details: {
206
+ query: queryToDict(query),
207
+ elementId: el.id,
208
+ inViewport: el.in_viewport,
209
+ isOccluded: el.is_occluded,
210
+ },
211
+ };
212
+ }
213
+ return {
214
+ passed: false,
215
+ reason: 'toBeVisible() requires ElementQuery',
216
+ details: {},
217
+ };
218
+ };
219
+ }
220
+ /**
221
+ * Assert that element's text contains the specified substring.
222
+ *
223
+ * @param text - Substring to search for (case-insensitive)
224
+ * @returns Predicate function for use with runtime.assert()
225
+ *
226
+ * @example
227
+ * await runtime.assert(
228
+ * expect(inDominantList().nth(0)).toHaveTextContains("Show HN"),
229
+ * "first_item_is_show_hn"
230
+ * );
231
+ */
232
+ toHaveTextContains(text) {
233
+ const query = this._query;
234
+ return (ctx) => {
235
+ const snap = ctx.snapshot;
236
+ if (!snap) {
237
+ return {
238
+ passed: false,
239
+ reason: 'no snapshot available',
240
+ details: { query: queryToDict(query), expectedText: text },
241
+ };
242
+ }
243
+ if (query instanceof query_1.ElementQuery) {
244
+ const matches = query.findAll(snap);
245
+ if (matches.length === 0) {
246
+ return {
247
+ passed: false,
248
+ reason: `no elements matched query: ${JSON.stringify(queryToDict(query))}`,
249
+ details: { query: queryToDict(query), matched: 0, expectedText: text },
250
+ };
251
+ }
252
+ // Check text of first match
253
+ const el = matches[0];
254
+ const elText = el.text || '';
255
+ const ok = elText.toLowerCase().includes(text.toLowerCase());
256
+ return {
257
+ passed: ok,
258
+ reason: ok ? '' : `element text '${elText.substring(0, 100)}' does not contain '${text}'`,
259
+ details: {
260
+ query: queryToDict(query),
261
+ elementId: el.id,
262
+ elementText: elText.substring(0, 200),
263
+ expectedText: text,
264
+ },
265
+ };
266
+ }
267
+ return {
268
+ passed: false,
269
+ reason: 'toHaveTextContains() requires ElementQuery',
270
+ details: {},
271
+ };
272
+ };
273
+ }
274
+ }
275
+ exports.ExpectBuilder = ExpectBuilder;
276
+ /**
277
+ * Factory for creating ExpectBuilder instances and global assertions.
278
+ *
279
+ * This is the main entry point for the assertion DSL.
280
+ *
281
+ * @example
282
+ * import { expect, E } from './asserts';
283
+ *
284
+ * // Element-based assertions
285
+ * expect(E({ role: "button" })).toExist()
286
+ * expect(E({ textContains: "Error" })).notToExist()
287
+ *
288
+ * // Global text assertions
289
+ * expect.textPresent("Welcome back")
290
+ * expect.noText("Error")
291
+ */
292
+ class ExpectFactory {
293
+ /**
294
+ * Create an expectation builder for the given query.
295
+ *
296
+ * @param query - ElementQuery, MultiQuery, or MultiTextPredicate
297
+ * @returns ExpectBuilder for chaining matchers
298
+ *
299
+ * @example
300
+ * expect(E({ role: "button" })).toExist()
301
+ * expect(inDominantList().nth(0)).toHaveTextContains("Show HN")
302
+ */
303
+ call(query) {
304
+ return new ExpectBuilder(query);
305
+ }
306
+ /**
307
+ * Global assertion: check if text is present anywhere on the page.
308
+ *
309
+ * Searches across all element text fields.
310
+ *
311
+ * @param text - Text to search for (case-insensitive substring)
312
+ * @returns Predicate function for use with runtime.assert()
313
+ *
314
+ * @example
315
+ * await runtime.assert(
316
+ * expect.textPresent("Welcome back"),
317
+ * "user_logged_in"
318
+ * );
319
+ */
320
+ textPresent(text) {
321
+ return (ctx) => {
322
+ const snap = ctx.snapshot;
323
+ if (!snap) {
324
+ return {
325
+ passed: false,
326
+ reason: 'no snapshot available',
327
+ details: { searchText: text },
328
+ };
329
+ }
330
+ // Search all element texts
331
+ const textLower = text.toLowerCase();
332
+ for (const el of snap.elements) {
333
+ const elText = el.text || '';
334
+ if (elText.toLowerCase().includes(textLower)) {
335
+ return {
336
+ passed: true,
337
+ reason: '',
338
+ details: { searchText: text, foundInElement: el.id },
339
+ };
340
+ }
341
+ }
342
+ return {
343
+ passed: false,
344
+ reason: `text '${text}' not found on page`,
345
+ details: { searchText: text, elementsSearched: snap.elements.length },
346
+ };
347
+ };
348
+ }
349
+ /**
350
+ * Global assertion: check that text is NOT present anywhere on the page.
351
+ *
352
+ * Searches across all element text fields.
353
+ *
354
+ * @param text - Text that should not be present (case-insensitive substring)
355
+ * @returns Predicate function for use with runtime.assert()
356
+ *
357
+ * @example
358
+ * await runtime.assert(
359
+ * expect.noText("Error"),
360
+ * "no_error_message"
361
+ * );
362
+ */
363
+ noText(text) {
364
+ return (ctx) => {
365
+ const snap = ctx.snapshot;
366
+ if (!snap) {
367
+ return {
368
+ passed: false,
369
+ reason: 'no snapshot available',
370
+ details: { searchText: text },
371
+ };
372
+ }
373
+ // Search all element texts
374
+ const textLower = text.toLowerCase();
375
+ for (const el of snap.elements) {
376
+ const elText = el.text || '';
377
+ if (elText.toLowerCase().includes(textLower)) {
378
+ return {
379
+ passed: false,
380
+ reason: `text '${text}' found in element id=${el.id}`,
381
+ details: {
382
+ searchText: text,
383
+ foundInElement: el.id,
384
+ elementText: elText.substring(0, 200),
385
+ },
386
+ };
387
+ }
388
+ }
389
+ return {
390
+ passed: true,
391
+ reason: '',
392
+ details: { searchText: text, elementsSearched: snap.elements.length },
393
+ };
394
+ };
395
+ }
396
+ }
397
+ // Create the singleton factory
398
+ const factoryInstance = new ExpectFactory();
399
+ /**
400
+ * Main entry point for the assertion DSL.
401
+ *
402
+ * Use as a function to create element-based assertions:
403
+ * expect(E({ role: "button" })).toExist()
404
+ *
405
+ * Use static methods for global assertions:
406
+ * expect.textPresent("Welcome")
407
+ * expect.noText("Error")
408
+ */
409
+ exports.expect = Object.assign((query) => factoryInstance.call(query), {
410
+ textPresent: (text) => factoryInstance.textPresent(text),
411
+ noText: (text) => factoryInstance.noText(text),
412
+ });
413
+ /**
414
+ * Wrapper that adds retry logic to a predicate.
415
+ *
416
+ * Created by withEventually(). Provides an async evaluate() method
417
+ * that retries the predicate with fresh snapshots.
418
+ *
419
+ * Note: TypeScript uses milliseconds for timeout/poll.
420
+ */
421
+ class EventuallyWrapper {
422
+ constructor(predicate, config = {}) {
423
+ this._predicate = predicate;
424
+ this._config = {
425
+ timeout: config.timeout ?? DEFAULT_TIMEOUT,
426
+ poll: config.poll ?? DEFAULT_POLL,
427
+ maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
428
+ };
429
+ }
430
+ /**
431
+ * Evaluate predicate with retry logic.
432
+ *
433
+ * @param ctx - Initial assertion context
434
+ * @param snapshotFn - Async function to take fresh snapshots
435
+ * @returns Promise resolving to AssertOutcome
436
+ */
437
+ async evaluate(ctx, snapshotFn) {
438
+ const startTime = Date.now();
439
+ let lastOutcome = null;
440
+ let attempts = 0;
441
+ while (true) {
442
+ // Check timeout (higher precedence than maxRetries)
443
+ const elapsed = Date.now() - startTime;
444
+ if (elapsed >= this._config.timeout) {
445
+ if (lastOutcome) {
446
+ lastOutcome.reason = `timeout after ${elapsed}ms: ${lastOutcome.reason}`;
447
+ return lastOutcome;
448
+ }
449
+ return {
450
+ passed: false,
451
+ reason: `timeout after ${elapsed}ms`,
452
+ details: { attempts },
453
+ };
454
+ }
455
+ // Check max retries
456
+ if (attempts >= this._config.maxRetries) {
457
+ if (lastOutcome) {
458
+ lastOutcome.reason = `max retries (${this._config.maxRetries}) exceeded: ${lastOutcome.reason}`;
459
+ return lastOutcome;
460
+ }
461
+ return {
462
+ passed: false,
463
+ reason: `max retries (${this._config.maxRetries}) exceeded`,
464
+ details: { attempts },
465
+ };
466
+ }
467
+ // Take fresh snapshot if not first attempt
468
+ if (attempts > 0) {
469
+ try {
470
+ const freshSnapshot = await snapshotFn();
471
+ ctx = {
472
+ snapshot: freshSnapshot,
473
+ url: freshSnapshot?.url ?? ctx.url,
474
+ stepId: ctx.stepId,
475
+ };
476
+ }
477
+ catch (e) {
478
+ lastOutcome = {
479
+ passed: false,
480
+ reason: `failed to take snapshot: ${e}`,
481
+ details: { attempts, error: String(e) },
482
+ };
483
+ attempts++;
484
+ await this.sleep(this._config.poll);
485
+ continue;
486
+ }
487
+ }
488
+ // Evaluate predicate
489
+ const outcome = this._predicate(ctx);
490
+ if (outcome.passed) {
491
+ outcome.details.attempts = attempts + 1;
492
+ return outcome;
493
+ }
494
+ lastOutcome = outcome;
495
+ attempts++;
496
+ // Wait before next retry
497
+ if (attempts < this._config.maxRetries) {
498
+ // Check if we'd exceed timeout with the poll delay
499
+ if (Date.now() - startTime + this._config.poll < this._config.timeout) {
500
+ await this.sleep(this._config.poll);
501
+ }
502
+ else {
503
+ // No point waiting, we'll timeout anyway
504
+ lastOutcome.reason = `timeout after ${Date.now() - startTime}ms: ${lastOutcome.reason}`;
505
+ return lastOutcome;
506
+ }
507
+ }
508
+ }
509
+ }
510
+ sleep(ms) {
511
+ return new Promise(resolve => setTimeout(resolve, ms));
512
+ }
513
+ /** Get the configured timeout in milliseconds */
514
+ get timeout() {
515
+ return this._config.timeout;
516
+ }
517
+ /** Get the configured poll interval in milliseconds */
518
+ get poll() {
519
+ return this._config.poll;
520
+ }
521
+ /** Get the configured max retries */
522
+ get maxRetries() {
523
+ return this._config.maxRetries;
524
+ }
525
+ }
526
+ exports.EventuallyWrapper = EventuallyWrapper;
527
+ /**
528
+ * Wrap a predicate with retry logic.
529
+ *
530
+ * This is the TypeScript API for .eventually(). Returns a wrapper
531
+ * that provides an async evaluate() method for use with the runtime.
532
+ *
533
+ * @param predicate - Predicate to wrap
534
+ * @param config - Retry configuration (timeout/poll in milliseconds)
535
+ * @returns EventuallyWrapper with async evaluate() method
536
+ *
537
+ * @example
538
+ * const wrapper = withEventually(
539
+ * expect(E({ role: "button" })).toExist(),
540
+ * { timeout: 5000, maxRetries: 10 }
541
+ * );
542
+ * const result = await wrapper.evaluate(ctx, runtime.snapshot);
543
+ */
544
+ function withEventually(predicate, config) {
545
+ return new EventuallyWrapper(predicate, config);
546
+ }
547
+ //# sourceMappingURL=expect.js.map