@react-three-dom/playwright 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -67,6 +67,11 @@ interface SceneSnapshot {
67
67
  objectCount: number;
68
68
  tree: SnapshotNode;
69
69
  }
70
+ declare global {
71
+ interface Window {
72
+ __R3F_DOM_DEBUG__?: boolean;
73
+ }
74
+ }
70
75
 
71
76
  interface WaitForSceneReadyOptions {
72
77
  /** How many consecutive stable polls are required. Default: 3 */
@@ -77,10 +82,32 @@ interface WaitForSceneReadyOptions {
77
82
  timeout?: number;
78
83
  }
79
84
  /**
80
- * Wait until `window.__R3F_DOM__` is available and the scene's object count
81
- * has stabilised (no additions or removals) over several consecutive checks.
85
+ * Wait until `window.__R3F_DOM__` is available, `_ready === true`, and the
86
+ * scene's object count has stabilised over several consecutive checks.
87
+ *
88
+ * If the bridge exists but `_ready` is false and `_error` is set, this
89
+ * fails immediately with a rich diagnostic message instead of timing out.
82
90
  */
83
91
  declare function waitForSceneReady(page: Page, options?: WaitForSceneReadyOptions): Promise<void>;
92
+ interface WaitForObjectOptions {
93
+ /** Time to wait for the bridge to appear. Default: 30_000 */
94
+ bridgeTimeout?: number;
95
+ /** Time to wait for the object to appear after bridge is ready. Default: 40_000 */
96
+ objectTimeout?: number;
97
+ /** Poll interval in ms. Default: 200 */
98
+ pollIntervalMs?: number;
99
+ }
100
+ /**
101
+ * Wait until `window.__R3F_DOM__` is ready and an object with the given
102
+ * testId or uuid exists in the scene.
103
+ *
104
+ * Use this instead of `waitForSceneReady` when the scene object count never
105
+ * stabilizes (e.g. continuous loading, animations adding/removing objects,
106
+ * or GLTF/models loading asynchronously).
107
+ *
108
+ * Fails fast if the bridge reports `_ready: false` with an `_error`.
109
+ */
110
+ declare function waitForObject(page: Page, idOrUuid: string, options?: WaitForObjectOptions): Promise<void>;
84
111
  interface WaitForIdleOptions {
85
112
  /** Number of consecutive idle frames required. Default: 10 */
86
113
  idleFrames?: number;
@@ -94,14 +121,82 @@ interface WaitForIdleOptions {
94
121
  * This works by taking successive snapshots and comparing them. When the
95
122
  * JSON representation is unchanged for `idleFrames` consecutive rAF
96
123
  * callbacks, the scene is considered idle.
124
+ *
125
+ * Checks `_ready === true` before starting. Fails fast if `_error` is set.
97
126
  */
98
127
  declare function waitForIdle(page: Page, options?: WaitForIdleOptions): Promise<void>;
128
+ interface WaitForNewObjectOptions {
129
+ /**
130
+ * Only consider new objects of this Three.js type (e.g. "Mesh", "Line").
131
+ * If not set, any new object type qualifies.
132
+ */
133
+ type?: string;
134
+ /**
135
+ * If provided, the new object's name must contain this substring.
136
+ * Useful for apps that name objects predictably (e.g. "stroke-", "wall-").
137
+ */
138
+ nameContains?: string;
139
+ /**
140
+ * Poll interval in ms. Default: 100
141
+ */
142
+ pollIntervalMs?: number;
143
+ /**
144
+ * Overall timeout in ms. Default: 10_000
145
+ */
146
+ timeout?: number;
147
+ }
148
+ /** Result returned when new objects are detected. */
149
+ interface WaitForNewObjectResult {
150
+ /** Metadata of all newly added objects (matching the filter). */
151
+ newObjects: ObjectMetadata[];
152
+ /** UUIDs of the newly added objects. */
153
+ newUuids: string[];
154
+ /** Total number of new objects detected. */
155
+ count: number;
156
+ }
157
+ /**
158
+ * Wait until one or more new objects appear in the scene that were not present
159
+ * at the time this function was called.
160
+ *
161
+ * This is designed for drawing/annotation apps where user interactions
162
+ * (like `drawPath`) create new Three.js objects asynchronously.
163
+ *
164
+ * @param page Playwright Page instance
165
+ * @param options Filter and timing options
166
+ * @returns Metadata of the newly added object(s)
167
+ * @throws If no new objects appear within the timeout
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * // Draw a stroke, then wait for the new Line object
172
+ * const before = await r3f.getCount();
173
+ * await r3f.drawPath(points);
174
+ * const result = await waitForNewObject(page, { type: 'Line' });
175
+ * expect(result.count).toBe(1);
176
+ * ```
177
+ */
178
+ declare function waitForNewObject(page: Page, options?: WaitForNewObjectOptions): Promise<WaitForNewObjectResult>;
99
179
 
180
+ /** Options for R3FFixture constructor. */
181
+ interface R3FFixtureOptions {
182
+ /** Auto-enable debug logging (forwards browser [r3f-dom:*] logs to test terminal). */
183
+ debug?: boolean;
184
+ }
100
185
  declare class R3FFixture {
101
186
  private readonly _page;
102
- constructor(_page: Page);
187
+ private _debugListenerAttached;
188
+ constructor(_page: Page, opts?: R3FFixtureOptions);
103
189
  /** The underlying Playwright Page. */
104
190
  get page(): Page;
191
+ /**
192
+ * Enable debug logging. Turns on `window.__R3F_DOM_DEBUG__` in the browser
193
+ * and forwards all `[r3f-dom:*]` console messages to the Node.js test terminal.
194
+ *
195
+ * Call before `page.goto()` to capture setup logs, or after to capture
196
+ * interaction logs.
197
+ */
198
+ enableDebug(): Promise<void>;
199
+ private _attachDebugListener;
105
200
  /** Get object metadata by testId or uuid. Returns null if not found. */
106
201
  getObject(idOrUuid: string): Promise<ObjectMetadata | null>;
107
202
  /** Get heavy inspection data (Tier 2) by testId or uuid. */
@@ -110,37 +205,142 @@ declare class R3FFixture {
110
205
  snapshot(): Promise<SceneSnapshot | null>;
111
206
  /** Get the total number of tracked objects. */
112
207
  getCount(): Promise<number>;
113
- /** Click a 3D object by testId or uuid. */
114
- click(idOrUuid: string): Promise<void>;
115
- /** Double-click a 3D object by testId or uuid. */
116
- doubleClick(idOrUuid: string): Promise<void>;
117
- /** Right-click / context-menu a 3D object by testId or uuid. */
118
- contextMenu(idOrUuid: string): Promise<void>;
119
- /** Hover over a 3D object by testId or uuid. */
120
- hover(idOrUuid: string): Promise<void>;
121
- /** Drag a 3D object with a world-space delta vector. */
208
+ /**
209
+ * Get all objects of a given Three.js type (e.g. "Mesh", "Group", "Line").
210
+ * Useful for BIM/CAD apps to find all walls, doors, etc. by object type.
211
+ */
212
+ getByType(type: string): Promise<ObjectMetadata[]>;
213
+ /**
214
+ * Get objects that have a specific userData key (and optionally matching value).
215
+ * Useful for BIM/CAD apps where objects are tagged with metadata like
216
+ * `userData.category = "wall"` or `userData.floorId = 2`.
217
+ */
218
+ getByUserData(key: string, value?: unknown): Promise<ObjectMetadata[]>;
219
+ /**
220
+ * Count objects of a given Three.js type.
221
+ * More efficient than `getByType(type).then(arr => arr.length)`.
222
+ */
223
+ getCountByType(type: string): Promise<number>;
224
+ /**
225
+ * Batch lookup: get metadata for multiple objects by testId or uuid in a
226
+ * single browser round-trip. Returns a record from id to metadata (or null).
227
+ *
228
+ * Much more efficient than calling `getObject()` in a loop for BIM/CAD
229
+ * scenes with many objects.
230
+ *
231
+ * @example
232
+ * ```typescript
233
+ * const results = await r3f.getObjects(['wall-1', 'door-2', 'window-3']);
234
+ * expect(results['wall-1']).not.toBeNull();
235
+ * expect(results['door-2']?.type).toBe('Mesh');
236
+ * ```
237
+ */
238
+ getObjects(ids: string[]): Promise<Record<string, ObjectMetadata | null>>;
239
+ /**
240
+ * Log the scene tree to the test terminal for debugging.
241
+ *
242
+ * Prints a human-readable tree like:
243
+ * ```
244
+ * Scene "root"
245
+ * ├─ Mesh "chair-primary" [testId: chair-primary] visible
246
+ * │ └─ BoxGeometry
247
+ * ├─ DirectionalLight "sun-light" [testId: sun-light] visible
248
+ * └─ Group "furniture"
249
+ * ├─ Mesh "table-top" [testId: table-top] visible
250
+ * └─ Mesh "vase" [testId: vase] visible
251
+ * ```
252
+ */
253
+ logScene(): Promise<void>;
254
+ /**
255
+ * Click a 3D object by testId or uuid.
256
+ * Auto-waits for the bridge and the object to exist before clicking.
257
+ * @param timeout Optional auto-wait timeout in ms. Default: 5000
258
+ */
259
+ click(idOrUuid: string, timeout?: number): Promise<void>;
260
+ /**
261
+ * Double-click a 3D object by testId or uuid.
262
+ * Auto-waits for the object to exist.
263
+ */
264
+ doubleClick(idOrUuid: string, timeout?: number): Promise<void>;
265
+ /**
266
+ * Right-click / context-menu a 3D object by testId or uuid.
267
+ * Auto-waits for the object to exist.
268
+ */
269
+ contextMenu(idOrUuid: string, timeout?: number): Promise<void>;
270
+ /**
271
+ * Hover over a 3D object by testId or uuid.
272
+ * Auto-waits for the object to exist.
273
+ */
274
+ hover(idOrUuid: string, timeout?: number): Promise<void>;
275
+ /**
276
+ * Drag a 3D object with a world-space delta vector.
277
+ * Auto-waits for the object to exist.
278
+ */
122
279
  drag(idOrUuid: string, delta: {
123
280
  x: number;
124
281
  y: number;
125
282
  z: number;
126
- }): Promise<void>;
127
- /** Dispatch a wheel/scroll event on a 3D object. */
283
+ }, timeout?: number): Promise<void>;
284
+ /**
285
+ * Dispatch a wheel/scroll event on a 3D object.
286
+ * Auto-waits for the object to exist.
287
+ */
128
288
  wheel(idOrUuid: string, options?: {
129
289
  deltaY?: number;
130
290
  deltaX?: number;
131
- }): Promise<void>;
132
- /** Click empty space to trigger onPointerMissed handlers. */
133
- pointerMiss(): Promise<void>;
291
+ }, timeout?: number): Promise<void>;
292
+ /**
293
+ * Click empty space to trigger onPointerMissed handlers.
294
+ * Auto-waits for the bridge to be ready.
295
+ */
296
+ pointerMiss(timeout?: number): Promise<void>;
297
+ /**
298
+ * Draw a freeform path on the canvas. Dispatches pointerdown → N × pointermove → pointerup.
299
+ * Designed for canvas drawing/annotation/whiteboard apps.
300
+ * Auto-waits for the bridge to be ready.
301
+ *
302
+ * @param points Array of screen-space points (min 2). { x, y } in CSS pixels relative to canvas.
303
+ * @param options Drawing options (stepDelayMs, pointerType, clickAtEnd)
304
+ * @param timeout Optional auto-wait timeout in ms. Default: 5000
305
+ * @returns { eventCount, pointCount }
306
+ */
307
+ drawPath(points: Array<{
308
+ x: number;
309
+ y: number;
310
+ pressure?: number;
311
+ }>, options?: {
312
+ stepDelayMs?: number;
313
+ pointerType?: 'mouse' | 'pen' | 'touch';
314
+ clickAtEnd?: boolean;
315
+ }, timeout?: number): Promise<{
316
+ eventCount: number;
317
+ pointCount: number;
318
+ }>;
134
319
  /**
135
320
  * Wait until the scene is ready — `window.__R3F_DOM__` is available and
136
321
  * the object count has stabilised across several consecutive checks.
137
322
  */
138
323
  waitForSceneReady(options?: WaitForSceneReadyOptions): Promise<void>;
324
+ /**
325
+ * Wait until the bridge is available and an object with the given testId or
326
+ * uuid exists. Use this instead of waitForSceneReady when the scene count
327
+ * never stabilizes (e.g. async model loading, continuous animations).
328
+ */
329
+ waitForObject(idOrUuid: string, options?: WaitForObjectOptions): Promise<void>;
139
330
  /**
140
331
  * Wait until no object properties have changed for a number of consecutive
141
332
  * animation frames. Useful after triggering interactions or animations.
142
333
  */
143
334
  waitForIdle(options?: WaitForIdleOptions): Promise<void>;
335
+ /**
336
+ * Wait until one or more new objects appear in the scene that were not
337
+ * present when this call was made. Perfect for drawing apps where
338
+ * `drawPath()` creates new geometry asynchronously.
339
+ *
340
+ * @param options Filter by type, nameContains, timeout, pollInterval
341
+ * @returns Metadata of the newly added object(s)
342
+ */
343
+ waitForNewObject(options?: WaitForNewObjectOptions): Promise<WaitForNewObjectResult>;
144
344
  /** Select a 3D object by testId or uuid (highlights in scene). */
145
345
  select(idOrUuid: string): Promise<void>;
146
346
  /** Clear the current selection. */
@@ -149,34 +349,42 @@ declare class R3FFixture {
149
349
  declare const test: _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & {
150
350
  r3f: R3FFixture;
151
351
  }, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>;
152
-
153
352
  /**
154
- * Extend Playwright's `expect` with 3D-native matchers.
353
+ * Create a custom test.extend with debug enabled for all tests:
155
354
  *
156
- * Usage:
157
- * ```ts
158
- * import { test, expect } from '@react-three-dom/playwright';
159
- *
160
- * test('chair exists', async ({ page, r3f }) => {
161
- * await r3f.waitForSceneReady();
162
- * await expect(r3f).toExist('chair-primary');
163
- * });
355
+ * ```typescript
356
+ * import { createR3FTest } from '@react-three-dom/playwright';
357
+ * export const test = createR3FTest({ debug: true });
164
358
  * ```
165
359
  */
360
+ declare function createR3FTest(options?: R3FFixtureOptions): _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & {
361
+ r3f: R3FFixture;
362
+ }, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>;
363
+
364
+ interface R3FMatcherReceiver {
365
+ page: Page;
366
+ getObject(idOrUuid: string): Promise<ObjectMetadata | null>;
367
+ inspect(idOrUuid: string): Promise<ObjectInspection | null>;
368
+ }
369
+ interface MatcherOptions {
370
+ timeout?: number;
371
+ interval?: number;
372
+ }
373
+ type Vec3Opts = MatcherOptions & {
374
+ tolerance?: number;
375
+ };
166
376
  declare const expect: _playwright_test.Expect<{
167
- toExist(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string): Promise<{
377
+ toExist(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, opts?: MatcherOptions): Promise<{
168
378
  pass: boolean;
169
379
  message: () => string;
170
380
  name: string;
171
381
  expected: string;
172
- actual: ObjectMetadata | null;
382
+ actual: null;
173
383
  }>;
174
- toBeVisible(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string): Promise<{
175
- pass: false;
384
+ toBeVisible(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, opts?: MatcherOptions): Promise<{
385
+ pass: boolean;
176
386
  message: () => string;
177
387
  name: string;
178
- expected?: undefined;
179
- actual?: undefined;
180
388
  } | {
181
389
  pass: boolean;
182
390
  message: () => string;
@@ -184,48 +392,141 @@ declare const expect: _playwright_test.Expect<{
184
392
  expected: boolean;
185
393
  actual: boolean;
186
394
  }>;
187
- toBeInFrustum(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string): Promise<{
395
+ toHavePosition(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expected: [number, number, number], tolOpts?: number | Vec3Opts): Promise<{
396
+ pass: boolean;
397
+ message: () => string;
398
+ name: string;
399
+ } | {
188
400
  pass: false;
189
401
  message: () => string;
190
402
  name: string;
191
- expected?: undefined;
192
- actual?: undefined;
403
+ expected: [number, number, number];
404
+ actual: [number, number, number];
405
+ }>;
406
+ toHaveRotation(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expected: [number, number, number], tolOpts?: number | Vec3Opts): Promise<{
407
+ pass: boolean;
408
+ message: () => string;
409
+ name: string;
193
410
  } | {
411
+ pass: false;
412
+ message: () => string;
413
+ name: string;
414
+ expected: [number, number, number];
415
+ actual: [number, number, number];
416
+ }>;
417
+ toHaveScale(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expected: [number, number, number], tolOpts?: number | Vec3Opts): Promise<{
194
418
  pass: boolean;
195
419
  message: () => string;
196
420
  name: string;
421
+ } | {
422
+ pass: false;
423
+ message: () => string;
424
+ name: string;
425
+ expected: [number, number, number];
426
+ actual: [number, number, number];
427
+ }>;
428
+ toHaveType(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedType: string, opts?: MatcherOptions): Promise<{
429
+ pass: boolean;
430
+ message: () => string;
431
+ name: string;
432
+ } | {
433
+ pass: false;
434
+ message: () => string;
435
+ name: string;
197
436
  expected: string;
198
- actual: {
199
- min: [number, number, number];
200
- max: [number, number, number];
201
- };
437
+ actual: string;
202
438
  }>;
203
- toHavePosition(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string, expected: [number, number, number], tolerance?: any): Promise<{
439
+ toHaveName(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedName: string, opts?: MatcherOptions): Promise<{
440
+ pass: boolean;
441
+ message: () => string;
442
+ name: string;
443
+ } | {
204
444
  pass: false;
205
445
  message: () => string;
206
446
  name: string;
207
- expected?: undefined;
208
- actual?: undefined;
447
+ expected: string;
448
+ actual: string;
449
+ }>;
450
+ toHaveGeometryType(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedGeo: string, opts?: MatcherOptions): Promise<{
451
+ pass: boolean;
452
+ message: () => string;
453
+ name: string;
209
454
  } | {
455
+ pass: false;
456
+ message: () => string;
457
+ name: string;
458
+ expected: string;
459
+ actual: string | undefined;
460
+ }>;
461
+ toHaveMaterialType(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedMat: string, opts?: MatcherOptions): Promise<{
210
462
  pass: boolean;
211
463
  message: () => string;
212
464
  name: string;
213
- expected: [number, number, number];
214
- actual: [number, number, number];
465
+ } | {
466
+ pass: false;
467
+ message: () => string;
468
+ name: string;
469
+ expected: string;
470
+ actual: string | undefined;
215
471
  }>;
216
- toHaveBounds(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string, expected: {
217
- min: [number, number, number];
218
- max: [number, number, number];
219
- }, tolerance?: any): Promise<{
472
+ toHaveChildCount(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedCount: number, opts?: MatcherOptions): Promise<{
473
+ pass: boolean;
474
+ message: () => string;
475
+ name: string;
476
+ } | {
477
+ pass: boolean;
478
+ message: () => string;
479
+ name: string;
480
+ expected: number;
481
+ actual: number;
482
+ }>;
483
+ toHaveParent(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedParent: string, opts?: MatcherOptions): Promise<{
484
+ pass: boolean;
485
+ message: () => string;
486
+ name: string;
487
+ } | {
220
488
  pass: false;
221
489
  message: () => string;
222
490
  name: string;
223
- expected?: undefined;
224
- actual?: undefined;
491
+ expected: string;
492
+ actual: string | null;
493
+ }>;
494
+ toHaveInstanceCount(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedCount: number, opts?: MatcherOptions): Promise<{
495
+ pass: boolean;
496
+ message: () => string;
497
+ name: string;
498
+ } | {
499
+ pass: boolean;
500
+ message: () => string;
501
+ name: string;
502
+ expected: number;
503
+ actual: number;
504
+ }>;
505
+ toBeInFrustum(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, opts?: MatcherOptions): Promise<{
506
+ pass: boolean;
507
+ message: () => string;
508
+ name: string;
225
509
  } | {
510
+ pass: false;
511
+ message: () => string;
512
+ name: string;
513
+ expected: string;
514
+ actual: {
515
+ min: [number, number, number];
516
+ max: [number, number, number];
517
+ };
518
+ }>;
519
+ toHaveBounds(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expected: {
520
+ min: [number, number, number];
521
+ max: [number, number, number];
522
+ }, tolOpts?: number | Vec3Opts): Promise<{
226
523
  pass: boolean;
227
524
  message: () => string;
228
525
  name: string;
526
+ } | {
527
+ pass: false;
528
+ message: () => string;
529
+ name: string;
229
530
  expected: {
230
531
  min: [number, number, number];
231
532
  max: [number, number, number];
@@ -235,12 +536,54 @@ declare const expect: _playwright_test.Expect<{
235
536
  max: [number, number, number];
236
537
  };
237
538
  }>;
238
- toHaveInstanceCount(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string, expectedCount: number): Promise<{
539
+ toHaveColor(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedColor: string, opts?: MatcherOptions): Promise<{
540
+ pass: boolean;
541
+ message: () => string;
542
+ name: string;
543
+ } | {
544
+ pass: false;
545
+ message: () => string;
546
+ name: string;
547
+ expected: string;
548
+ actual: string | undefined;
549
+ }>;
550
+ toHaveOpacity(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedOpacity: number, tolOpts?: number | Vec3Opts): Promise<{
551
+ pass: boolean;
552
+ message: () => string;
553
+ name: string;
554
+ } | {
555
+ pass: false;
556
+ message: () => string;
557
+ name: string;
558
+ expected: number;
559
+ actual: number | undefined;
560
+ }>;
561
+ toBeTransparent(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, opts?: MatcherOptions): Promise<{
562
+ pass: boolean;
563
+ message: () => string;
564
+ name: string;
565
+ } | {
239
566
  pass: false;
240
567
  message: () => string;
241
568
  name: string;
242
- expected?: undefined;
243
- actual?: undefined;
569
+ expected: boolean;
570
+ actual: boolean | undefined;
571
+ }>;
572
+ toHaveVertexCount(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedCount: number, opts?: MatcherOptions): Promise<{
573
+ pass: boolean;
574
+ message: () => string;
575
+ name: string;
576
+ } | {
577
+ pass: boolean;
578
+ message: () => string;
579
+ name: string;
580
+ expected: number;
581
+ actual: number;
582
+ }>;
583
+ toHaveTriangleCount(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedCount: number, opts?: MatcherOptions): Promise<{
584
+ pass: boolean;
585
+ message: () => string;
586
+ name: string;
244
587
  } | {
245
588
  pass: boolean;
246
589
  message: () => string;
@@ -248,32 +591,203 @@ declare const expect: _playwright_test.Expect<{
248
591
  expected: number;
249
592
  actual: number;
250
593
  }>;
594
+ toHaveUserData(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, key: string, expectedValue?: unknown, opts?: MatcherOptions): Promise<{
595
+ pass: boolean;
596
+ message: () => string;
597
+ name: string;
598
+ } | {
599
+ pass: false;
600
+ message: () => string;
601
+ name: string;
602
+ expected: {};
603
+ actual: unknown;
604
+ }>;
605
+ toHaveMapTexture(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedName?: string, opts?: MatcherOptions): Promise<{
606
+ pass: boolean;
607
+ message: () => string;
608
+ name: string;
609
+ } | {
610
+ pass: false;
611
+ message: () => string;
612
+ name: string;
613
+ expected: string;
614
+ actual: string | undefined;
615
+ }>;
616
+ /**
617
+ * Assert the total number of objects in the scene.
618
+ * Auto-retries until the count matches or timeout.
619
+ *
620
+ * @example expect(r3f).toHaveObjectCount(42);
621
+ * @example expect(r3f).toHaveObjectCount(42, { timeout: 10_000 });
622
+ */
623
+ toHaveObjectCount(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, expected: number, options?: MatcherOptions): Promise<{
624
+ pass: false;
625
+ message: () => string;
626
+ name: string;
627
+ expected: number;
628
+ actual: number;
629
+ }>;
630
+ /**
631
+ * Assert the total number of objects is at least `min`.
632
+ * Useful for BIM scenes where the exact count may vary slightly.
633
+ *
634
+ * @example expect(r3f).toHaveObjectCountGreaterThan(10);
635
+ */
636
+ toHaveObjectCountGreaterThan(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, min: number, options?: MatcherOptions): Promise<{
637
+ pass: false;
638
+ message: () => string;
639
+ name: string;
640
+ expected: string;
641
+ actual: number;
642
+ }>;
643
+ /**
644
+ * Assert the count of objects of a specific Three.js type.
645
+ * Auto-retries until the count matches or timeout.
646
+ *
647
+ * @example expect(r3f).toHaveCountByType('Mesh', 5);
648
+ * @example expect(r3f).toHaveCountByType('Line', 10, { timeout: 10_000 });
649
+ */
650
+ toHaveCountByType(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, type: string, expected: number, options?: MatcherOptions): Promise<{
651
+ pass: false;
652
+ message: () => string;
653
+ name: string;
654
+ expected: number;
655
+ actual: number;
656
+ }>;
657
+ /**
658
+ * Assert the total triangle count across all meshes in the scene.
659
+ * Use as a performance budget guard — fail if the scene exceeds a threshold.
660
+ *
661
+ * @example expect(r3f).toHaveTotalTriangleCount(50000);
662
+ * @example expect(r3f).not.toHaveTotalTriangleCountGreaterThan(100000); // budget guard
663
+ */
664
+ toHaveTotalTriangleCount(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, expected: number, options?: MatcherOptions): Promise<{
665
+ pass: false;
666
+ message: () => string;
667
+ name: string;
668
+ expected: number;
669
+ actual: number;
670
+ }>;
671
+ /**
672
+ * Assert the total triangle count is at most `max`.
673
+ * Perfect as a performance budget guard to prevent scene bloat.
674
+ *
675
+ * @example expect(r3f).toHaveTotalTriangleCountLessThan(100_000);
676
+ */
677
+ toHaveTotalTriangleCountLessThan(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, max: number, options?: MatcherOptions): Promise<{
678
+ pass: false;
679
+ message: () => string;
680
+ name: string;
681
+ expected: string;
682
+ actual: number;
683
+ }>;
251
684
  }>;
252
- interface R3FMatcherReceiver {
253
- getObject(idOrUuid: string): Promise<ObjectMetadata | null>;
254
- inspect(idOrUuid: string): Promise<ObjectInspection | null>;
255
- }
256
685
 
257
- /** Click a 3D object by its testId or uuid. */
258
- declare function click(page: Page, idOrUuid: string): Promise<void>;
259
- /** Double-click a 3D object by its testId or uuid. */
260
- declare function doubleClick(page: Page, idOrUuid: string): Promise<void>;
261
- /** Right-click / context-menu a 3D object by its testId or uuid. */
262
- declare function contextMenu(page: Page, idOrUuid: string): Promise<void>;
263
- /** Hover over a 3D object by its testId or uuid. */
264
- declare function hover(page: Page, idOrUuid: string): Promise<void>;
265
- /** Drag a 3D object by its testId or uuid with a given world-space delta. */
686
+ /** Click a 3D object by its testId or uuid. Auto-waits for the object. */
687
+ declare function click(page: Page, idOrUuid: string, timeout?: number): Promise<void>;
688
+ /** Double-click a 3D object by its testId or uuid. Auto-waits for the object. */
689
+ declare function doubleClick(page: Page, idOrUuid: string, timeout?: number): Promise<void>;
690
+ /** Right-click / context-menu a 3D object. Auto-waits for the object. */
691
+ declare function contextMenu(page: Page, idOrUuid: string, timeout?: number): Promise<void>;
692
+ /** Hover over a 3D object. Auto-waits for the object. */
693
+ declare function hover(page: Page, idOrUuid: string, timeout?: number): Promise<void>;
694
+ /** Drag a 3D object with a world-space delta. Auto-waits for the object. */
266
695
  declare function drag(page: Page, idOrUuid: string, delta: {
267
696
  x: number;
268
697
  y: number;
269
698
  z: number;
270
- }): Promise<void>;
271
- /** Dispatch a wheel/scroll event on a 3D object. */
699
+ }, timeout?: number): Promise<void>;
700
+ /** Dispatch a wheel/scroll event on a 3D object. Auto-waits for the object. */
272
701
  declare function wheel(page: Page, idOrUuid: string, options?: {
273
702
  deltaY?: number;
274
703
  deltaX?: number;
275
- }): Promise<void>;
276
- /** Click empty space to trigger onPointerMissed handlers. */
277
- declare function pointerMiss(page: Page): Promise<void>;
704
+ }, timeout?: number): Promise<void>;
705
+ /** Click empty space to trigger onPointerMissed handlers. Auto-waits for bridge. */
706
+ declare function pointerMiss(page: Page, timeout?: number): Promise<void>;
707
+ /** Draw a freeform path on the canvas. Auto-waits for bridge. */
708
+ declare function drawPathOnCanvas(page: Page, points: Array<{
709
+ x: number;
710
+ y: number;
711
+ pressure?: number;
712
+ }>, options?: {
713
+ stepDelayMs?: number;
714
+ pointerType?: 'mouse' | 'pen' | 'touch';
715
+ clickAtEnd?: boolean;
716
+ }, timeout?: number): Promise<{
717
+ eventCount: number;
718
+ pointCount: number;
719
+ }>;
720
+
721
+ /** A 2D screen-space point in CSS pixels, relative to the canvas top-left. */
722
+ interface DrawPoint {
723
+ /** X coordinate in CSS pixels from canvas left edge. */
724
+ x: number;
725
+ /** Y coordinate in CSS pixels from canvas top edge. */
726
+ y: number;
727
+ /** Optional pressure (0–1). Default: 0.5 */
728
+ pressure?: number;
729
+ }
730
+ /**
731
+ * Generate points along a straight line.
732
+ *
733
+ * @param start Start point
734
+ * @param end End point
735
+ * @param steps Intermediate points (excluding start/end). Default: 10
736
+ * @param pressure Uniform pressure. Default: 0.5
737
+ */
738
+ declare function linePath(start: {
739
+ x: number;
740
+ y: number;
741
+ }, end: {
742
+ x: number;
743
+ y: number;
744
+ }, steps?: number, pressure?: number): DrawPoint[];
745
+ /**
746
+ * Generate points along a quadratic bezier curve.
747
+ *
748
+ * @param start Start point
749
+ * @param control Control point
750
+ * @param end End point
751
+ * @param steps Number of steps. Default: 20
752
+ * @param pressure Uniform pressure. Default: 0.5
753
+ */
754
+ declare function curvePath(start: {
755
+ x: number;
756
+ y: number;
757
+ }, control: {
758
+ x: number;
759
+ y: number;
760
+ }, end: {
761
+ x: number;
762
+ y: number;
763
+ }, steps?: number, pressure?: number): DrawPoint[];
764
+ /**
765
+ * Generate points forming a rectangle.
766
+ *
767
+ * @param topLeft Top-left corner
768
+ * @param bottomRight Bottom-right corner
769
+ * @param pointsPerSide Points per side. Default: 5
770
+ * @param pressure Uniform pressure. Default: 0.5
771
+ */
772
+ declare function rectPath(topLeft: {
773
+ x: number;
774
+ y: number;
775
+ }, bottomRight: {
776
+ x: number;
777
+ y: number;
778
+ }, pointsPerSide?: number, pressure?: number): DrawPoint[];
779
+ /**
780
+ * Generate points forming a circle/ellipse.
781
+ *
782
+ * @param center Center point
783
+ * @param radiusX Horizontal radius in CSS pixels
784
+ * @param radiusY Vertical radius. Default: same as radiusX
785
+ * @param steps Number of points. Default: 36
786
+ * @param pressure Uniform pressure. Default: 0.5
787
+ */
788
+ declare function circlePath(center: {
789
+ x: number;
790
+ y: number;
791
+ }, radiusX: number, radiusY?: number, steps?: number, pressure?: number): DrawPoint[];
278
792
 
279
- export { type ObjectInspection, type ObjectMetadata, R3FFixture, type SceneSnapshot, type SnapshotNode, type WaitForIdleOptions, type WaitForSceneReadyOptions, click, contextMenu, doubleClick, drag, expect, hover, pointerMiss, test, waitForIdle, waitForSceneReady, wheel };
793
+ export { type DrawPoint, type ObjectInspection, type ObjectMetadata, R3FFixture, type R3FFixtureOptions, type SceneSnapshot, type SnapshotNode, type WaitForIdleOptions, type WaitForNewObjectOptions, type WaitForNewObjectResult, type WaitForObjectOptions, type WaitForSceneReadyOptions, circlePath, click, contextMenu, createR3FTest, curvePath, doubleClick, drag, drawPathOnCanvas, expect, hover, linePath, pointerMiss, rectPath, test, waitForIdle, waitForNewObject, waitForObject, waitForSceneReady, wheel };