@react-three-dom/playwright 0.1.2 → 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,8 +82,11 @@ 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>;
84
92
  interface WaitForObjectOptions {
@@ -90,12 +98,14 @@ interface WaitForObjectOptions {
90
98
  pollIntervalMs?: number;
91
99
  }
92
100
  /**
93
- * Wait until `window.__R3F_DOM__` is available and an object with the given
101
+ * Wait until `window.__R3F_DOM__` is ready and an object with the given
94
102
  * testId or uuid exists in the scene.
95
103
  *
96
104
  * Use this instead of `waitForSceneReady` when the scene object count never
97
105
  * stabilizes (e.g. continuous loading, animations adding/removing objects,
98
106
  * or GLTF/models loading asynchronously).
107
+ *
108
+ * Fails fast if the bridge reports `_ready: false` with an `_error`.
99
109
  */
100
110
  declare function waitForObject(page: Page, idOrUuid: string, options?: WaitForObjectOptions): Promise<void>;
101
111
  interface WaitForIdleOptions {
@@ -111,14 +121,82 @@ interface WaitForIdleOptions {
111
121
  * This works by taking successive snapshots and comparing them. When the
112
122
  * JSON representation is unchanged for `idleFrames` consecutive rAF
113
123
  * callbacks, the scene is considered idle.
124
+ *
125
+ * Checks `_ready === true` before starting. Fails fast if `_error` is set.
114
126
  */
115
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>;
116
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
+ }
117
185
  declare class R3FFixture {
118
186
  private readonly _page;
119
- constructor(_page: Page);
187
+ private _debugListenerAttached;
188
+ constructor(_page: Page, opts?: R3FFixtureOptions);
120
189
  /** The underlying Playwright Page. */
121
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;
122
200
  /** Get object metadata by testId or uuid. Returns null if not found. */
123
201
  getObject(idOrUuid: string): Promise<ObjectMetadata | null>;
124
202
  /** Get heavy inspection data (Tier 2) by testId or uuid. */
@@ -127,27 +205,117 @@ declare class R3FFixture {
127
205
  snapshot(): Promise<SceneSnapshot | null>;
128
206
  /** Get the total number of tracked objects. */
129
207
  getCount(): Promise<number>;
130
- /** Click a 3D object by testId or uuid. */
131
- click(idOrUuid: string): Promise<void>;
132
- /** Double-click a 3D object by testId or uuid. */
133
- doubleClick(idOrUuid: string): Promise<void>;
134
- /** Right-click / context-menu a 3D object by testId or uuid. */
135
- contextMenu(idOrUuid: string): Promise<void>;
136
- /** Hover over a 3D object by testId or uuid. */
137
- hover(idOrUuid: string): Promise<void>;
138
- /** 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
+ */
139
279
  drag(idOrUuid: string, delta: {
140
280
  x: number;
141
281
  y: number;
142
282
  z: number;
143
- }): Promise<void>;
144
- /** 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
+ */
145
288
  wheel(idOrUuid: string, options?: {
146
289
  deltaY?: number;
147
290
  deltaX?: number;
148
- }): Promise<void>;
149
- /** Click empty space to trigger onPointerMissed handlers. */
150
- 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
+ }>;
151
319
  /**
152
320
  * Wait until the scene is ready — `window.__R3F_DOM__` is available and
153
321
  * the object count has stabilised across several consecutive checks.
@@ -164,6 +332,15 @@ declare class R3FFixture {
164
332
  * animation frames. Useful after triggering interactions or animations.
165
333
  */
166
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>;
167
344
  /** Select a 3D object by testId or uuid (highlights in scene). */
168
345
  select(idOrUuid: string): Promise<void>;
169
346
  /** Clear the current selection. */
@@ -172,34 +349,42 @@ declare class R3FFixture {
172
349
  declare const test: _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & {
173
350
  r3f: R3FFixture;
174
351
  }, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>;
175
-
176
352
  /**
177
- * Extend Playwright's `expect` with 3D-native matchers.
353
+ * Create a custom test.extend with debug enabled for all tests:
178
354
  *
179
- * Usage:
180
- * ```ts
181
- * import { test, expect } from '@react-three-dom/playwright';
182
- *
183
- * test('chair exists', async ({ page, r3f }) => {
184
- * await r3f.waitForSceneReady();
185
- * await expect(r3f).toExist('chair-primary');
186
- * });
355
+ * ```typescript
356
+ * import { createR3FTest } from '@react-three-dom/playwright';
357
+ * export const test = createR3FTest({ debug: true });
187
358
  * ```
188
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
+ };
189
376
  declare const expect: _playwright_test.Expect<{
190
- toExist(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string): Promise<{
377
+ toExist(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, opts?: MatcherOptions): Promise<{
191
378
  pass: boolean;
192
379
  message: () => string;
193
380
  name: string;
194
381
  expected: string;
195
- actual: ObjectMetadata | null;
382
+ actual: null;
196
383
  }>;
197
- toBeVisible(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string): Promise<{
198
- pass: false;
384
+ toBeVisible(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, opts?: MatcherOptions): Promise<{
385
+ pass: boolean;
199
386
  message: () => string;
200
387
  name: string;
201
- expected?: undefined;
202
- actual?: undefined;
203
388
  } | {
204
389
  pass: boolean;
205
390
  message: () => string;
@@ -207,48 +392,141 @@ declare const expect: _playwright_test.Expect<{
207
392
  expected: boolean;
208
393
  actual: boolean;
209
394
  }>;
210
- 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
+ } | {
400
+ pass: false;
401
+ message: () => string;
402
+ name: string;
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;
410
+ } | {
211
411
  pass: false;
212
412
  message: () => string;
213
413
  name: string;
214
- expected?: undefined;
215
- actual?: undefined;
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<{
418
+ pass: boolean;
419
+ message: () => string;
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;
216
432
  } | {
433
+ pass: false;
434
+ message: () => string;
435
+ name: string;
436
+ expected: string;
437
+ actual: string;
438
+ }>;
439
+ toHaveName(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedName: string, opts?: MatcherOptions): Promise<{
217
440
  pass: boolean;
218
441
  message: () => string;
219
442
  name: string;
443
+ } | {
444
+ pass: false;
445
+ message: () => string;
446
+ name: string;
220
447
  expected: string;
221
- actual: {
222
- min: [number, number, number];
223
- max: [number, number, number];
224
- };
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;
454
+ } | {
455
+ pass: false;
456
+ message: () => string;
457
+ name: string;
458
+ expected: string;
459
+ actual: string | undefined;
225
460
  }>;
226
- toHavePosition(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string, expected: [number, number, number], tolerance?: any): Promise<{
461
+ toHaveMaterialType(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedMat: string, opts?: MatcherOptions): Promise<{
462
+ pass: boolean;
463
+ message: () => string;
464
+ name: string;
465
+ } | {
227
466
  pass: false;
228
467
  message: () => string;
229
468
  name: string;
230
- expected?: undefined;
231
- actual?: undefined;
469
+ expected: string;
470
+ actual: string | undefined;
471
+ }>;
472
+ toHaveChildCount(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, id: string, expectedCount: number, opts?: MatcherOptions): Promise<{
473
+ pass: boolean;
474
+ message: () => string;
475
+ name: string;
232
476
  } | {
233
477
  pass: boolean;
234
478
  message: () => string;
235
479
  name: string;
236
- expected: [number, number, number];
237
- actual: [number, number, number];
480
+ expected: number;
481
+ actual: number;
238
482
  }>;
239
- toHaveBounds(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string, expected: {
240
- min: [number, number, number];
241
- max: [number, number, number];
242
- }, tolerance?: any): Promise<{
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
+ } | {
243
488
  pass: false;
244
489
  message: () => string;
245
490
  name: string;
246
- expected?: undefined;
247
- 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;
248
498
  } | {
249
499
  pass: boolean;
250
500
  message: () => string;
251
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;
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<{
523
+ pass: boolean;
524
+ message: () => string;
525
+ name: string;
526
+ } | {
527
+ pass: false;
528
+ message: () => string;
529
+ name: string;
252
530
  expected: {
253
531
  min: [number, number, number];
254
532
  max: [number, number, number];
@@ -258,12 +536,43 @@ declare const expect: _playwright_test.Expect<{
258
536
  max: [number, number, number];
259
537
  };
260
538
  }>;
261
- 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
+ } | {
262
566
  pass: false;
263
567
  message: () => string;
264
568
  name: string;
265
- expected?: undefined;
266
- 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;
267
576
  } | {
268
577
  pass: boolean;
269
578
  message: () => string;
@@ -271,32 +580,214 @@ declare const expect: _playwright_test.Expect<{
271
580
  expected: number;
272
581
  actual: number;
273
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;
587
+ } | {
588
+ pass: boolean;
589
+ message: () => string;
590
+ name: string;
591
+ expected: number;
592
+ actual: number;
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
+ }>;
274
684
  }>;
275
- interface R3FMatcherReceiver {
276
- getObject(idOrUuid: string): Promise<ObjectMetadata | null>;
277
- inspect(idOrUuid: string): Promise<ObjectInspection | null>;
278
- }
279
685
 
280
- /** Click a 3D object by its testId or uuid. */
281
- declare function click(page: Page, idOrUuid: string): Promise<void>;
282
- /** Double-click a 3D object by its testId or uuid. */
283
- declare function doubleClick(page: Page, idOrUuid: string): Promise<void>;
284
- /** Right-click / context-menu a 3D object by its testId or uuid. */
285
- declare function contextMenu(page: Page, idOrUuid: string): Promise<void>;
286
- /** Hover over a 3D object by its testId or uuid. */
287
- declare function hover(page: Page, idOrUuid: string): Promise<void>;
288
- /** 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. */
289
695
  declare function drag(page: Page, idOrUuid: string, delta: {
290
696
  x: number;
291
697
  y: number;
292
698
  z: number;
293
- }): Promise<void>;
294
- /** 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. */
295
701
  declare function wheel(page: Page, idOrUuid: string, options?: {
296
702
  deltaY?: number;
297
703
  deltaX?: number;
298
- }): Promise<void>;
299
- /** Click empty space to trigger onPointerMissed handlers. */
300
- 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[];
301
792
 
302
- export { type ObjectInspection, type ObjectMetadata, R3FFixture, type SceneSnapshot, type SnapshotNode, type WaitForIdleOptions, type WaitForObjectOptions, type WaitForSceneReadyOptions, click, contextMenu, doubleClick, drag, expect, hover, pointerMiss, test, waitForIdle, waitForObject, 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 };