@react-three-dom/playwright 0.1.2 → 0.3.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
@@ -1,5 +1,5 @@
1
1
  import * as _playwright_test from '@playwright/test';
2
- import { Page } from '@playwright/test';
2
+ import { Page, Locator } from '@playwright/test';
3
3
 
4
4
  interface ObjectMetadata {
5
5
  uuid: string;
@@ -12,6 +12,10 @@ interface ObjectMetadata {
12
12
  vertexCount?: number;
13
13
  triangleCount?: number;
14
14
  instanceCount?: number;
15
+ fov?: number;
16
+ near?: number;
17
+ far?: number;
18
+ zoom?: number;
15
19
  position: [number, number, number];
16
20
  rotation: [number, number, number];
17
21
  scale: [number, number, number];
@@ -19,6 +23,10 @@ interface ObjectMetadata {
19
23
  childrenUuids: string[];
20
24
  boundsDirty: boolean;
21
25
  }
26
+ /** Options for inspect(). Set includeGeometryData: true to get vertex/index buffers (higher cost). */
27
+ interface InspectOptions {
28
+ includeGeometryData?: boolean;
29
+ }
22
30
  interface ObjectInspection {
23
31
  metadata: ObjectMetadata;
24
32
  worldMatrix: number[];
@@ -39,6 +47,10 @@ interface ObjectInspection {
39
47
  center: [number, number, number];
40
48
  radius: number;
41
49
  };
50
+ /** Vertex positions (x,y,z per vertex). Only when inspect(..., { includeGeometryData: true }). */
51
+ positionData?: number[];
52
+ /** Triangle indices. Only when inspect(..., { includeGeometryData: true }) and geometry is indexed. */
53
+ indexData?: number[];
42
54
  };
43
55
  material?: {
44
56
  type: string;
@@ -67,6 +79,138 @@ interface SceneSnapshot {
67
79
  objectCount: number;
68
80
  tree: SnapshotNode;
69
81
  }
82
+ interface CameraState {
83
+ type: string;
84
+ position: [number, number, number];
85
+ rotation: [number, number, number];
86
+ target: [number, number, number];
87
+ fov?: number;
88
+ near: number;
89
+ far: number;
90
+ zoom: number;
91
+ aspect?: number;
92
+ left?: number;
93
+ right?: number;
94
+ top?: number;
95
+ bottom?: number;
96
+ }
97
+ interface BridgeDiagnostics {
98
+ version: string;
99
+ ready: boolean;
100
+ error?: string;
101
+ objectCount: number;
102
+ meshCount: number;
103
+ groupCount: number;
104
+ lightCount: number;
105
+ cameraCount: number;
106
+ materializedDomNodes: number;
107
+ maxDomNodes: number;
108
+ canvasWidth: number;
109
+ canvasHeight: number;
110
+ webglRenderer: string;
111
+ dirtyQueueSize: number;
112
+ }
113
+ declare global {
114
+ interface Window {
115
+ __R3F_DOM_DEBUG__?: boolean;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * @module reporter
121
+ *
122
+ * Rich terminal reporter for Playwright tests. Outputs ANSI-colored status
123
+ * messages for bridge lifecycle events (waiting, connected, error), scene
124
+ * readiness, object lookups (with fuzzy-match suggestions on miss),
125
+ * interaction timings, assertion failures, and full bridge diagnostics.
126
+ */
127
+
128
+ /**
129
+ * Formatted terminal reporter for react-three-dom Playwright tests.
130
+ * Logs bridge lifecycle, scene readiness, interaction timings, assertion
131
+ * failures, and full diagnostics with ANSI colors.
132
+ */
133
+ declare class R3FReporter {
134
+ private readonly _page;
135
+ private _enabled;
136
+ private _canvasId?;
137
+ constructor(_page: Page, enabled?: boolean, canvasId?: string);
138
+ logBridgeWaiting(): void;
139
+ logBridgeConnected(diag?: BridgeDiagnostics): void;
140
+ logBridgeError(error: string): void;
141
+ logSceneReady(objectCount: number): void;
142
+ logObjectFound(idOrUuid: string, type: string, name?: string): void;
143
+ logObjectNotFound(idOrUuid: string, suggestions?: Array<{
144
+ testId?: string;
145
+ name: string;
146
+ uuid: string;
147
+ }>): void;
148
+ logInteraction(action: string, idOrUuid: string, extra?: string): void;
149
+ logInteractionDone(action: string, idOrUuid: string, durationMs: number): void;
150
+ logAssertionFailure(matcherName: string, id: string, detail: string, diag?: BridgeDiagnostics): void;
151
+ logDiagnostics(): Promise<void>;
152
+ fetchDiagnostics(): Promise<BridgeDiagnostics | null>;
153
+ fetchFuzzyMatches(query: string, limit?: number): Promise<Array<{
154
+ testId?: string;
155
+ name: string;
156
+ uuid: string;
157
+ }>>;
158
+ private _printDiagnosticsSummary;
159
+ private _printDiagnosticsFull;
160
+ }
161
+
162
+ /**
163
+ * @module diffSnapshots
164
+ *
165
+ * Pure scene-diff utility. Compares two {@link SceneSnapshot}s by UUID and
166
+ * returns added nodes, removed nodes, and per-field property changes
167
+ * (name, type, testId, visible, position, rotation, scale).
168
+ *
169
+ * Stateless and side-effect-free — safe to call from any context.
170
+ */
171
+
172
+ /** Describes a single property change on an object that exists in both snapshots. */
173
+ interface SceneDiffChange {
174
+ uuid: string;
175
+ field: string;
176
+ from: unknown;
177
+ to: unknown;
178
+ }
179
+ /** Result of diffing two scene snapshots. */
180
+ interface SceneDiff {
181
+ /** Nodes present in `after` but not in `before` (from `after` tree). */
182
+ added: SnapshotNode[];
183
+ /** Nodes present in `before` but not in `after` (from `before` tree). */
184
+ removed: SnapshotNode[];
185
+ /** Property changes for nodes that exist in both; each entry is one field that changed. */
186
+ changed: SceneDiffChange[];
187
+ }
188
+ /**
189
+ * Compare two scene snapshots and return added nodes, removed nodes, and
190
+ * property changes for nodes that exist in both.
191
+ *
192
+ * - **added**: nodes in `after` whose uuid was not in `before`.
193
+ * - **removed**: nodes in `before` whose uuid was not in `after`.
194
+ * - **changed**: for each uuid present in both, lists field-level changes
195
+ * (name, type, testId, visible, position, rotation, scale).
196
+ */
197
+ declare function diffSnapshots(before: SceneSnapshot, after: SceneSnapshot): SceneDiff;
198
+
199
+ /**
200
+ * @module waiters
201
+ *
202
+ * Polling-based wait utilities for Playwright tests. Each waiter polls the
203
+ * `window.__R3F_DOM__` bridge until a condition is met or a timeout fires.
204
+ *
205
+ * - {@link waitForSceneReady} — bridge ready + object count stabilised
206
+ * - {@link waitForObject} — bridge ready + specific object exists
207
+ * - {@link waitForIdle} — no property changes for N consecutive frames
208
+ * - {@link waitForNewObject} — new object(s) appear after a baseline snapshot
209
+ * - {@link waitForObjectRemoved} — object no longer in the scene
210
+ *
211
+ * All waiters fail fast with a rich diagnostic if the bridge reports an
212
+ * `_error` state, preventing silent timeouts.
213
+ */
70
214
 
71
215
  interface WaitForSceneReadyOptions {
72
216
  /** How many consecutive stable polls are required. Default: 3 */
@@ -77,10 +221,15 @@ interface WaitForSceneReadyOptions {
77
221
  timeout?: number;
78
222
  }
79
223
  /**
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.
224
+ * Wait until `window.__R3F_DOM__` is available, `_ready === true`, and the
225
+ * scene's object count has stabilised over several consecutive checks.
226
+ *
227
+ * If the bridge exists but `_ready` is false and `_error` is set, this
228
+ * fails immediately with a rich diagnostic message instead of timing out.
82
229
  */
83
- declare function waitForSceneReady(page: Page, options?: WaitForSceneReadyOptions): Promise<void>;
230
+ declare function waitForSceneReady(page: Page, options?: WaitForSceneReadyOptions & {
231
+ canvasId?: string;
232
+ }): Promise<void>;
84
233
  interface WaitForObjectOptions {
85
234
  /** Time to wait for the bridge to appear. Default: 30_000 */
86
235
  bridgeTimeout?: number;
@@ -90,14 +239,18 @@ interface WaitForObjectOptions {
90
239
  pollIntervalMs?: number;
91
240
  }
92
241
  /**
93
- * Wait until `window.__R3F_DOM__` is available and an object with the given
242
+ * Wait until `window.__R3F_DOM__` is ready and an object with the given
94
243
  * testId or uuid exists in the scene.
95
244
  *
96
245
  * Use this instead of `waitForSceneReady` when the scene object count never
97
246
  * stabilizes (e.g. continuous loading, animations adding/removing objects,
98
247
  * or GLTF/models loading asynchronously).
248
+ *
249
+ * Fails fast if the bridge reports `_ready: false` with an `_error`.
99
250
  */
100
- declare function waitForObject(page: Page, idOrUuid: string, options?: WaitForObjectOptions): Promise<void>;
251
+ declare function waitForObject(page: Page, idOrUuid: string, options?: WaitForObjectOptions & {
252
+ canvasId?: string;
253
+ }): Promise<void>;
101
254
  interface WaitForIdleOptions {
102
255
  /** Number of consecutive idle frames required. Default: 10 */
103
256
  idleFrames?: number;
@@ -111,46 +264,317 @@ interface WaitForIdleOptions {
111
264
  * This works by taking successive snapshots and comparing them. When the
112
265
  * JSON representation is unchanged for `idleFrames` consecutive rAF
113
266
  * callbacks, the scene is considered idle.
267
+ *
268
+ * Checks `_ready === true` before starting. Fails fast if `_error` is set.
269
+ */
270
+ declare function waitForIdle(page: Page, options?: WaitForIdleOptions & {
271
+ canvasId?: string;
272
+ }): Promise<void>;
273
+ interface WaitForNewObjectOptions {
274
+ /**
275
+ * Only consider new objects of this Three.js type (e.g. "Mesh", "Line").
276
+ * If not set, any new object type qualifies.
277
+ */
278
+ type?: string;
279
+ /**
280
+ * If provided, the new object's name must contain this substring.
281
+ * Useful for apps that name objects predictably (e.g. "stroke-", "wall-").
282
+ */
283
+ nameContains?: string;
284
+ /**
285
+ * Poll interval in ms. Default: 100
286
+ */
287
+ pollIntervalMs?: number;
288
+ /**
289
+ * Overall timeout in ms. Default: 10_000
290
+ */
291
+ timeout?: number;
292
+ }
293
+ /** Result returned when new objects are detected. */
294
+ interface WaitForNewObjectResult {
295
+ /** Metadata of all newly added objects (matching the filter). */
296
+ newObjects: ObjectMetadata[];
297
+ /** UUIDs of the newly added objects. */
298
+ newUuids: string[];
299
+ /** Total number of new objects detected. */
300
+ count: number;
301
+ }
302
+ /**
303
+ * Wait until one or more new objects appear in the scene that were not present
304
+ * at the time this function was called.
305
+ *
306
+ * This is designed for drawing/annotation apps where user interactions
307
+ * (like `drawPath`) create new Three.js objects asynchronously.
308
+ *
309
+ * @param page Playwright Page instance
310
+ * @param options Filter and timing options
311
+ * @returns Metadata of the newly added object(s)
312
+ * @throws If no new objects appear within the timeout
313
+ *
314
+ * @example
315
+ * ```typescript
316
+ * // Draw a stroke, then wait for the new Line object
317
+ * const before = await r3f.getCount();
318
+ * await r3f.drawPath(points);
319
+ * const result = await waitForNewObject(page, { type: 'Line' });
320
+ * expect(result.count).toBe(1);
321
+ * ```
114
322
  */
115
- declare function waitForIdle(page: Page, options?: WaitForIdleOptions): Promise<void>;
323
+ declare function waitForNewObject(page: Page, options?: WaitForNewObjectOptions & {
324
+ canvasId?: string;
325
+ }): Promise<WaitForNewObjectResult>;
326
+ interface WaitForObjectRemovedOptions {
327
+ /** Time to wait for the bridge to appear. Default: 30_000 */
328
+ bridgeTimeout?: number;
329
+ /** Poll interval in ms. Default: 100 */
330
+ pollIntervalMs?: number;
331
+ /** Overall timeout in ms (after bridge is ready). Default: 10_000 */
332
+ timeout?: number;
333
+ }
334
+ /**
335
+ * Wait until the bridge is ready and an object with the given testId or uuid
336
+ * is no longer in the scene. Use for delete flows (e.g. user deletes an object,
337
+ * then you assert it's gone).
338
+ *
339
+ * @param page Playwright Page instance
340
+ * @param idOrUuid testId or uuid of the object that should be removed
341
+ * @param options Timing options
342
+ * @throws If the object is still present after the timeout
343
+ *
344
+ * @example
345
+ * ```ts
346
+ * await r3f.click('delete-button');
347
+ * await r3f.waitForObjectRemoved('item-to-delete');
348
+ * await expect(r3f).not.toExist('item-to-delete');
349
+ * ```
350
+ */
351
+ declare function waitForObjectRemoved(page: Page, idOrUuid: string, options?: WaitForObjectRemovedOptions & {
352
+ canvasId?: string;
353
+ }): Promise<void>;
116
354
 
355
+ /** Options for R3FFixture constructor. */
356
+ interface R3FFixtureOptions {
357
+ /** Auto-enable debug logging (forwards browser [r3f-dom:*] logs to test terminal). */
358
+ debug?: boolean;
359
+ /**
360
+ * Enable rich diagnostic reporting in the terminal.
361
+ * Logs bridge status, scene readiness, interaction details, and
362
+ * failure context automatically. Default: true.
363
+ */
364
+ report?: boolean;
365
+ /** Target a specific canvas by its canvasId. When omitted, uses the default bridge. */
366
+ canvasId?: string;
367
+ }
368
+ /**
369
+ * Main API object provided to Playwright tests for interacting with a
370
+ * react-three-dom scene. Wraps queries, interactions, waiters, snapshot
371
+ * diffing, and rich terminal diagnostics. Supports multi-canvas apps
372
+ * via {@link R3FFixture.forCanvas}.
373
+ */
117
374
  declare class R3FFixture {
118
375
  private readonly _page;
119
- constructor(_page: Page);
376
+ private _debugListenerAttached;
377
+ private readonly _reporter;
378
+ /** Canvas ID for multi-canvas apps. Undefined = default bridge. */
379
+ readonly canvasId?: string;
380
+ constructor(_page: Page, opts?: R3FFixtureOptions);
120
381
  /** The underlying Playwright Page. */
121
382
  get page(): Page;
383
+ /** Access the reporter for custom diagnostic logging. */
384
+ get reporter(): R3FReporter;
385
+ /**
386
+ * Create a scoped fixture targeting a specific canvas instance.
387
+ * All queries, interactions, and assertions on the returned fixture
388
+ * will use `window.__R3F_DOM_INSTANCES__[canvasId]` instead of
389
+ * `window.__R3F_DOM__`.
390
+ *
391
+ * @example
392
+ * ```typescript
393
+ * const mainR3f = r3f.forCanvas('main-viewport');
394
+ * const minimapR3f = r3f.forCanvas('minimap');
395
+ * await mainR3f.click('building-42');
396
+ * await expect(minimapR3f).toExist('building-42-marker');
397
+ * ```
398
+ */
399
+ forCanvas(canvasId: string): R3FFixture;
400
+ /**
401
+ * List all active canvas IDs registered on the page.
402
+ * Returns an empty array if only the default (unnamed) bridge is active.
403
+ */
404
+ getCanvasIds(): Promise<string[]>;
405
+ /**
406
+ * Enable debug logging. Turns on `window.__R3F_DOM_DEBUG__` in the browser
407
+ * and forwards all `[r3f-dom:*]` console messages to the Node.js test terminal.
408
+ *
409
+ * Call before `page.goto()` to capture setup logs, or after to capture
410
+ * interaction logs.
411
+ */
412
+ enableDebug(): Promise<void>;
413
+ private _attachDebugListener;
122
414
  /** Get object metadata by testId or uuid. Returns null if not found. */
123
415
  getObject(idOrUuid: string): Promise<ObjectMetadata | null>;
124
- /** Get heavy inspection data (Tier 2) by testId or uuid. */
125
- inspect(idOrUuid: string): Promise<ObjectInspection | null>;
416
+ /** Get object metadata by testId (userData.testId). Returns null if not found. */
417
+ getByTestId(testId: string): Promise<ObjectMetadata | null>;
418
+ /** Get object metadata by UUID. Returns null if not found. */
419
+ getByUuid(uuid: string): Promise<ObjectMetadata | null>;
420
+ /** Get all objects with the given name (names are not unique in Three.js). */
421
+ getByName(name: string): Promise<ObjectMetadata[]>;
422
+ /** Get direct children of an object by testId or uuid. */
423
+ getChildren(idOrUuid: string): Promise<ObjectMetadata[]>;
424
+ /** Get parent of an object by testId or uuid. Returns null if root or not found. */
425
+ getParent(idOrUuid: string): Promise<ObjectMetadata | null>;
426
+ /** Get heavy inspection data (Tier 2) by testId or uuid. Pass { includeGeometryData: true } to include vertex positions and triangle indices. */
427
+ inspect(idOrUuid: string, options?: {
428
+ includeGeometryData?: boolean;
429
+ }): Promise<ObjectInspection | null>;
430
+ /**
431
+ * Get world-space position [x, y, z] of an object (from its world matrix).
432
+ * Use for nested objects where local position differs from world position.
433
+ */
434
+ getWorldPosition(idOrUuid: string): Promise<[number, number, number] | null>;
126
435
  /** Take a full scene snapshot. */
127
436
  snapshot(): Promise<SceneSnapshot | null>;
437
+ /**
438
+ * Compare two scene snapshots: returns added nodes, removed nodes, and
439
+ * property changes (name, type, testId, visible, position, rotation, scale).
440
+ * Use after taking snapshots before/after an action to assert on scene changes.
441
+ */
442
+ diffSnapshots(before: SceneSnapshot, after: SceneSnapshot): SceneDiff;
443
+ /**
444
+ * Run an async action and return how many objects were added and removed
445
+ * compared to before the action. Uses snapshots before/after so add and
446
+ * remove are both counted correctly when both happen.
447
+ */
448
+ trackObjectCount(action: () => Promise<void>): Promise<{
449
+ added: number;
450
+ removed: number;
451
+ }>;
128
452
  /** Get the total number of tracked objects. */
129
453
  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. */
454
+ /**
455
+ * Return a Playwright locator for the R3F canvas element the bridge is attached to.
456
+ * The canvas has `data-r3f-canvas` set by the bridge (value is the canvasId or "true").
457
+ */
458
+ getCanvasLocator(): Locator;
459
+ /**
460
+ * Get all objects of a given Three.js type (e.g. "Mesh", "Group", "Line").
461
+ */
462
+ getByType(type: string): Promise<ObjectMetadata[]>;
463
+ /**
464
+ * Get all objects with a given geometry type (e.g. "BoxGeometry", "BufferGeometry").
465
+ */
466
+ getByGeometryType(type: string): Promise<ObjectMetadata[]>;
467
+ /**
468
+ * Get all objects with a given material type (e.g. "MeshStandardMaterial").
469
+ */
470
+ getByMaterialType(type: string): Promise<ObjectMetadata[]>;
471
+ /**
472
+ * Get objects that have a specific userData key (and optionally matching value).
473
+ */
474
+ getByUserData(key: string, value?: unknown): Promise<ObjectMetadata[]>;
475
+ /**
476
+ * Count objects of a given Three.js type.
477
+ */
478
+ getCountByType(type: string): Promise<number>;
479
+ /**
480
+ * Batch lookup: get metadata for multiple objects by testId or uuid in a
481
+ * single browser round-trip.
482
+ */
483
+ getObjects(ids: string[]): Promise<Record<string, ObjectMetadata | null>>;
484
+ /**
485
+ * Log the scene tree to the test terminal for debugging.
486
+ *
487
+ * Prints a human-readable tree like:
488
+ * ```
489
+ * Scene "root"
490
+ * ├─ Mesh "chair-primary" [testId: chair-primary] visible
491
+ * │ └─ BoxGeometry
492
+ * ├─ DirectionalLight "sun-light" [testId: sun-light] visible
493
+ * └─ Group "furniture"
494
+ * ├─ Mesh "table-top" [testId: table-top] visible
495
+ * └─ Mesh "vase" [testId: vase] visible
496
+ * ```
497
+ */
498
+ logScene(): Promise<void>;
499
+ /**
500
+ * Click a 3D object by testId or uuid.
501
+ * Auto-waits for the bridge and the object to exist before clicking.
502
+ * @param timeout Optional auto-wait timeout in ms. Default: 5000
503
+ */
504
+ click(idOrUuid: string, timeout?: number): Promise<void>;
505
+ /**
506
+ * Double-click a 3D object by testId or uuid.
507
+ * Auto-waits for the object to exist.
508
+ */
509
+ doubleClick(idOrUuid: string, timeout?: number): Promise<void>;
510
+ /**
511
+ * Right-click / context-menu a 3D object by testId or uuid.
512
+ * Auto-waits for the object to exist.
513
+ */
514
+ contextMenu(idOrUuid: string, timeout?: number): Promise<void>;
515
+ /**
516
+ * Hover over a 3D object by testId or uuid.
517
+ * Auto-waits for the object to exist.
518
+ */
519
+ hover(idOrUuid: string, timeout?: number): Promise<void>;
520
+ /**
521
+ * Unhover / pointer-leave — resets hover state by moving pointer off-canvas.
522
+ * Auto-waits for the bridge to be ready.
523
+ */
524
+ unhover(timeout?: number): Promise<void>;
525
+ /**
526
+ * Drag a 3D object with a world-space delta vector.
527
+ * Auto-waits for the object to exist.
528
+ */
139
529
  drag(idOrUuid: string, delta: {
140
530
  x: number;
141
531
  y: number;
142
532
  z: number;
143
- }): Promise<void>;
144
- /** Dispatch a wheel/scroll event on a 3D object. */
533
+ }, timeout?: number): Promise<void>;
534
+ /**
535
+ * Dispatch a wheel/scroll event on a 3D object.
536
+ * Auto-waits for the object to exist.
537
+ */
145
538
  wheel(idOrUuid: string, options?: {
146
539
  deltaY?: number;
147
540
  deltaX?: number;
148
- }): Promise<void>;
149
- /** Click empty space to trigger onPointerMissed handlers. */
150
- pointerMiss(): Promise<void>;
541
+ }, timeout?: number): Promise<void>;
542
+ /**
543
+ * Click empty space to trigger onPointerMissed handlers.
544
+ * Auto-waits for the bridge to be ready.
545
+ */
546
+ pointerMiss(timeout?: number): Promise<void>;
547
+ /**
548
+ * Draw a freeform path on the canvas. Dispatches pointerdown → N × pointermove → pointerup.
549
+ * Designed for canvas drawing/annotation/whiteboard apps.
550
+ * Auto-waits for the bridge to be ready.
551
+ *
552
+ * @param points Array of screen-space points (min 2). { x, y } in CSS pixels relative to canvas.
553
+ * @param options Drawing options (stepDelayMs, pointerType, clickAtEnd)
554
+ * @param timeout Optional auto-wait timeout in ms. Default: 5000
555
+ * @returns { eventCount, pointCount }
556
+ */
557
+ drawPath(points: Array<{
558
+ x: number;
559
+ y: number;
560
+ pressure?: number;
561
+ }>, options?: {
562
+ stepDelayMs?: number;
563
+ pointerType?: 'mouse' | 'pen' | 'touch';
564
+ clickAtEnd?: boolean;
565
+ }, timeout?: number): Promise<{
566
+ eventCount: number;
567
+ pointCount: number;
568
+ }>;
569
+ /**
570
+ * Get the current camera state (position, rotation, fov, near, far, zoom, target).
571
+ * Auto-waits for the bridge to be ready.
572
+ */
573
+ getCameraState(timeout?: number): Promise<CameraState>;
151
574
  /**
152
575
  * Wait until the scene is ready — `window.__R3F_DOM__` is available and
153
576
  * the object count has stabilised across several consecutive checks.
577
+ * Logs bridge connection and scene readiness to the terminal.
154
578
  */
155
579
  waitForSceneReady(options?: WaitForSceneReadyOptions): Promise<void>;
156
580
  /**
@@ -164,42 +588,106 @@ declare class R3FFixture {
164
588
  * animation frames. Useful after triggering interactions or animations.
165
589
  */
166
590
  waitForIdle(options?: WaitForIdleOptions): Promise<void>;
591
+ /**
592
+ * Wait until one or more new objects appear in the scene that were not
593
+ * present when this call was made. Perfect for drawing apps where
594
+ * `drawPath()` creates new geometry asynchronously.
595
+ *
596
+ * @param options Filter by type, nameContains, timeout, pollInterval
597
+ * @returns Metadata of the newly added object(s)
598
+ */
599
+ waitForNewObject(options?: WaitForNewObjectOptions): Promise<WaitForNewObjectResult>;
600
+ /**
601
+ * Wait until an object (by testId or uuid) is no longer in the scene.
602
+ * Use for delete flows: trigger removal, then wait until the object is gone.
603
+ */
604
+ waitForObjectRemoved(idOrUuid: string, options?: WaitForObjectRemovedOptions): Promise<void>;
167
605
  /** Select a 3D object by testId or uuid (highlights in scene). */
168
606
  select(idOrUuid: string): Promise<void>;
169
607
  /** Clear the current selection. */
170
608
  clearSelection(): Promise<void>;
609
+ /**
610
+ * Fetch full bridge diagnostics (version, object counts, GPU info, etc.).
611
+ * Returns null if the bridge is not available.
612
+ */
613
+ getDiagnostics(): Promise<BridgeDiagnostics | null>;
614
+ /**
615
+ * Print a full diagnostics report to the terminal.
616
+ * Useful at the start of a test suite or when debugging failures.
617
+ */
618
+ logDiagnostics(): Promise<void>;
171
619
  }
172
620
  declare const test: _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & {
173
621
  r3f: R3FFixture;
174
622
  }, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>;
623
+ /**
624
+ * Create a custom test.extend with debug enabled for all tests:
625
+ *
626
+ * ```typescript
627
+ * import { createR3FTest } from '@react-three-dom/playwright';
628
+ * export const test = createR3FTest({ debug: true });
629
+ * ```
630
+ */
631
+ declare function createR3FTest(options?: R3FFixtureOptions): _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & {
632
+ r3f: R3FFixture;
633
+ }, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>;
175
634
 
176
635
  /**
177
- * Extend Playwright's `expect` with 3D-native matchers.
636
+ * @module assertions
178
637
  *
179
- * Usage:
180
- * ```ts
181
- * import { test, expect } from '@react-three-dom/playwright';
638
+ * Custom Playwright `expect` matchers for 3D scene testing via react-three-dom.
182
639
  *
183
- * test('chair exists', async ({ page, r3f }) => {
184
- * await r3f.waitForSceneReady();
185
- * await expect(r3f).toExist('chair-primary');
186
- * });
187
- * ```
640
+ * Every matcher auto-retries until the assertion passes or the timeout
641
+ * expires, matching Playwright's built-in assertion behaviour.
642
+ *
643
+ * **Tier 1 — Metadata:** toExist, toBeVisible, toHavePosition,
644
+ * toHaveWorldPosition, toHaveRotation, toHaveScale, toHaveType, toHaveName,
645
+ * toHaveGeometryType, toHaveMaterialType, toHaveChildCount, toHaveParent,
646
+ * toHaveInstanceCount
647
+ *
648
+ * **Tier 2 — Inspection:** toBeInFrustum, toHaveBounds, toHaveColor,
649
+ * toHaveOpacity, toBeTransparent, toHaveVertexCount, toHaveTriangleCount,
650
+ * toHaveUserData, toHaveMapTexture
651
+ *
652
+ * **Scene-level:** toHaveObjectCount, toHaveObjectCountGreaterThan,
653
+ * toHaveCountByType, toHaveTotalTriangleCount,
654
+ * toHaveTotalTriangleCountLessThan
655
+ *
656
+ * **Camera:** toHaveCameraPosition, toHaveCameraFov, toHaveCameraNear,
657
+ * toHaveCameraFar, toHaveCameraZoom
658
+ *
659
+ * **Batch:** toAllExist, toAllBeVisible, toNoneExist
188
660
  */
189
- declare const expect: _playwright_test.Expect<{
190
- toExist(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string): Promise<{
661
+
662
+ interface R3FMatcherReceiver {
663
+ page: Page;
664
+ canvasId?: string;
665
+ getObject(idOrUuid: string): Promise<ObjectMetadata | null>;
666
+ inspect(idOrUuid: string): Promise<ObjectInspection | null>;
667
+ }
668
+ /** Context provided by Playwright when the matcher is invoked via expect().extend() */
669
+ interface ExpectMatcherContext {
670
+ isNot?: boolean;
671
+ }
672
+ interface MatcherOptions {
673
+ timeout?: number;
674
+ interval?: number;
675
+ }
676
+ type Vec3Opts = MatcherOptions & {
677
+ tolerance?: number;
678
+ };
679
+ declare const r3fMatchers: {
680
+ toExist(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, opts?: MatcherOptions): Promise<{
191
681
  pass: boolean;
192
682
  message: () => string;
193
683
  name: string;
194
684
  expected: string;
195
- actual: ObjectMetadata | null;
685
+ actual: null;
196
686
  }>;
197
- toBeVisible(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string): Promise<{
198
- pass: false;
687
+ toBeVisible(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, opts?: MatcherOptions): Promise<{
688
+ pass: boolean;
199
689
  message: () => string;
200
690
  name: string;
201
- expected?: undefined;
202
- actual?: undefined;
203
691
  } | {
204
692
  pass: boolean;
205
693
  message: () => string;
@@ -207,28 +695,43 @@ declare const expect: _playwright_test.Expect<{
207
695
  expected: boolean;
208
696
  actual: boolean;
209
697
  }>;
210
- toBeInFrustum(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string): Promise<{
211
- pass: false;
698
+ toHavePosition(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expected: [number, number, number], tolOpts?: number | Vec3Opts): Promise<{
699
+ pass: boolean;
212
700
  message: () => string;
213
701
  name: string;
214
- expected?: undefined;
215
- actual?: undefined;
216
702
  } | {
217
703
  pass: boolean;
218
704
  message: () => string;
219
705
  name: string;
220
- expected: string;
221
- actual: {
222
- min: [number, number, number];
223
- max: [number, number, number];
224
- };
706
+ expected: [number, number, number];
707
+ actual: [number, number, number];
708
+ }>;
709
+ toHaveWorldPosition(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expected: [number, number, number], tolOpts?: number | Vec3Opts): Promise<{
710
+ pass: boolean;
711
+ message: () => string;
712
+ name: string;
713
+ } | {
714
+ pass: boolean;
715
+ message: () => string;
716
+ name: string;
717
+ expected: [number, number, number];
718
+ actual: [number, number, number];
719
+ }>;
720
+ toHaveRotation(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expected: [number, number, number], tolOpts?: number | Vec3Opts): Promise<{
721
+ pass: boolean;
722
+ message: () => string;
723
+ name: string;
724
+ } | {
725
+ pass: boolean;
726
+ message: () => string;
727
+ name: string;
728
+ expected: [number, number, number];
729
+ actual: [number, number, number];
225
730
  }>;
226
- toHavePosition(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string, expected: [number, number, number], tolerance?: any): Promise<{
227
- pass: false;
731
+ toHaveScale(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expected: [number, number, number], tolOpts?: number | Vec3Opts): Promise<{
732
+ pass: boolean;
228
733
  message: () => string;
229
734
  name: string;
230
- expected?: undefined;
231
- actual?: undefined;
232
735
  } | {
233
736
  pass: boolean;
234
737
  message: () => string;
@@ -236,15 +739,104 @@ declare const expect: _playwright_test.Expect<{
236
739
  expected: [number, number, number];
237
740
  actual: [number, number, number];
238
741
  }>;
239
- toHaveBounds(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string, expected: {
742
+ toHaveType(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expectedType: string, opts?: MatcherOptions): Promise<{
743
+ pass: boolean;
744
+ message: () => string;
745
+ name: string;
746
+ } | {
747
+ pass: boolean;
748
+ message: () => string;
749
+ name: string;
750
+ expected: string;
751
+ actual: string;
752
+ }>;
753
+ toHaveName(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expectedName: string, opts?: MatcherOptions): Promise<{
754
+ pass: boolean;
755
+ message: () => string;
756
+ name: string;
757
+ } | {
758
+ pass: boolean;
759
+ message: () => string;
760
+ name: string;
761
+ expected: string;
762
+ actual: string;
763
+ }>;
764
+ toHaveGeometryType(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expectedGeo: string, opts?: MatcherOptions): Promise<{
765
+ pass: boolean;
766
+ message: () => string;
767
+ name: string;
768
+ } | {
769
+ pass: boolean;
770
+ message: () => string;
771
+ name: string;
772
+ expected: string;
773
+ actual: string | undefined;
774
+ }>;
775
+ toHaveMaterialType(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expectedMat: string, opts?: MatcherOptions): Promise<{
776
+ pass: boolean;
777
+ message: () => string;
778
+ name: string;
779
+ } | {
780
+ pass: boolean;
781
+ message: () => string;
782
+ name: string;
783
+ expected: string;
784
+ actual: string | undefined;
785
+ }>;
786
+ toHaveChildCount(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expectedCount: number, opts?: MatcherOptions): Promise<{
787
+ pass: boolean;
788
+ message: () => string;
789
+ name: string;
790
+ } | {
791
+ pass: boolean;
792
+ message: () => string;
793
+ name: string;
794
+ expected: number;
795
+ actual: number;
796
+ }>;
797
+ toHaveParent(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expectedParent: string, opts?: MatcherOptions): Promise<{
798
+ pass: boolean;
799
+ message: () => string;
800
+ name: string;
801
+ } | {
802
+ pass: boolean;
803
+ message: () => string;
804
+ name: string;
805
+ expected: string;
806
+ actual: string | null;
807
+ }>;
808
+ toHaveInstanceCount(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expectedCount: number, opts?: MatcherOptions): Promise<{
809
+ pass: boolean;
810
+ message: () => string;
811
+ name: string;
812
+ } | {
813
+ pass: boolean;
814
+ message: () => string;
815
+ name: string;
816
+ expected: number;
817
+ actual: number;
818
+ }>;
819
+ toBeInFrustum(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, opts?: MatcherOptions): Promise<{
820
+ pass: boolean;
821
+ message: () => string;
822
+ name: string;
823
+ } | {
824
+ pass: boolean;
825
+ message: () => string;
826
+ name: string;
827
+ expected: string;
828
+ actual: {
829
+ min: [number, number, number];
830
+ max: [number, number, number];
831
+ };
832
+ }>;
833
+ toHaveBounds(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expected: {
240
834
  min: [number, number, number];
241
835
  max: [number, number, number];
242
- }, tolerance?: any): Promise<{
243
- pass: false;
836
+ }, tolOpts?: number | Vec3Opts): Promise<{
837
+ pass: boolean;
244
838
  message: () => string;
245
839
  name: string;
246
- expected?: undefined;
247
- actual?: undefined;
248
840
  } | {
249
841
  pass: boolean;
250
842
  message: () => string;
@@ -258,12 +850,43 @@ declare const expect: _playwright_test.Expect<{
258
850
  max: [number, number, number];
259
851
  };
260
852
  }>;
261
- toHaveInstanceCount(this: _playwright_test.ExpectMatcherState, r3f: R3FMatcherReceiver, idOrUuid: string, expectedCount: number): Promise<{
262
- pass: false;
853
+ toHaveColor(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expectedColor: string, opts?: MatcherOptions): Promise<{
854
+ pass: boolean;
855
+ message: () => string;
856
+ name: string;
857
+ } | {
858
+ pass: boolean;
859
+ message: () => string;
860
+ name: string;
861
+ expected: string;
862
+ actual: string | undefined;
863
+ }>;
864
+ toHaveOpacity(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expectedOpacity: number, tolOpts?: number | Vec3Opts): Promise<{
865
+ pass: boolean;
866
+ message: () => string;
867
+ name: string;
868
+ } | {
869
+ pass: boolean;
870
+ message: () => string;
871
+ name: string;
872
+ expected: number;
873
+ actual: number | undefined;
874
+ }>;
875
+ toBeTransparent(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, opts?: MatcherOptions): Promise<{
876
+ pass: boolean;
877
+ message: () => string;
878
+ name: string;
879
+ } | {
880
+ pass: boolean;
881
+ message: () => string;
882
+ name: string;
883
+ expected: boolean;
884
+ actual: boolean | undefined;
885
+ }>;
886
+ toHaveVertexCount(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expectedCount: number, opts?: MatcherOptions): Promise<{
887
+ pass: boolean;
263
888
  message: () => string;
264
889
  name: string;
265
- expected?: undefined;
266
- actual?: undefined;
267
890
  } | {
268
891
  pass: boolean;
269
892
  message: () => string;
@@ -271,32 +894,349 @@ declare const expect: _playwright_test.Expect<{
271
894
  expected: number;
272
895
  actual: number;
273
896
  }>;
274
- }>;
275
- interface R3FMatcherReceiver {
276
- getObject(idOrUuid: string): Promise<ObjectMetadata | null>;
277
- inspect(idOrUuid: string): Promise<ObjectInspection | null>;
278
- }
897
+ toHaveTriangleCount(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expectedCount: number, opts?: MatcherOptions): Promise<{
898
+ pass: boolean;
899
+ message: () => string;
900
+ name: string;
901
+ } | {
902
+ pass: boolean;
903
+ message: () => string;
904
+ name: string;
905
+ expected: number;
906
+ actual: number;
907
+ }>;
908
+ toHaveUserData(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, key: string, expectedValue?: unknown, opts?: MatcherOptions): Promise<{
909
+ pass: boolean;
910
+ message: () => string;
911
+ name: string;
912
+ } | {
913
+ pass: boolean;
914
+ message: () => string;
915
+ name: string;
916
+ expected: {};
917
+ actual: unknown;
918
+ }>;
919
+ toHaveMapTexture(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, id: string, expectedName?: string, opts?: MatcherOptions): Promise<{
920
+ pass: boolean;
921
+ message: () => string;
922
+ name: string;
923
+ } | {
924
+ pass: boolean;
925
+ message: () => string;
926
+ name: string;
927
+ expected: string;
928
+ actual: string | undefined;
929
+ }>;
930
+ /**
931
+ * Assert the total number of objects in the scene.
932
+ * Auto-retries until the count matches or timeout.
933
+ *
934
+ * @example expect(r3f).toHaveObjectCount(42);
935
+ * @example expect(r3f).toHaveObjectCount(42, { timeout: 10_000 });
936
+ */
937
+ toHaveObjectCount(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, expected: number, options?: MatcherOptions): Promise<{
938
+ pass: boolean;
939
+ message: () => string;
940
+ name: string;
941
+ expected: number;
942
+ actual: number;
943
+ }>;
944
+ /**
945
+ * Assert the total number of objects is at least `min`.
946
+ * Useful for BIM scenes where the exact count may vary slightly.
947
+ *
948
+ * @example expect(r3f).toHaveObjectCountGreaterThan(10);
949
+ */
950
+ toHaveObjectCountGreaterThan(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, min: number, options?: MatcherOptions): Promise<{
951
+ pass: boolean;
952
+ message: () => string;
953
+ name: string;
954
+ expected: string;
955
+ actual: number;
956
+ }>;
957
+ /**
958
+ * Assert the count of objects of a specific Three.js type.
959
+ * Auto-retries until the count matches or timeout.
960
+ *
961
+ * @example expect(r3f).toHaveCountByType('Mesh', 5);
962
+ * @example expect(r3f).toHaveCountByType('Line', 10, { timeout: 10_000 });
963
+ */
964
+ toHaveCountByType(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, type: string, expected: number, options?: MatcherOptions): Promise<{
965
+ pass: boolean;
966
+ message: () => string;
967
+ name: string;
968
+ expected: number;
969
+ actual: number;
970
+ }>;
971
+ /**
972
+ * Assert the total triangle count across all meshes in the scene.
973
+ * Use as a performance budget guard — fail if the scene exceeds a threshold.
974
+ *
975
+ * @example expect(r3f).toHaveTotalTriangleCount(50000);
976
+ * @example expect(r3f).not.toHaveTotalTriangleCountGreaterThan(100000); // budget guard
977
+ */
978
+ toHaveTotalTriangleCount(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, expected: number, options?: MatcherOptions): Promise<{
979
+ pass: boolean;
980
+ message: () => string;
981
+ name: string;
982
+ expected: number;
983
+ actual: number;
984
+ }>;
985
+ /**
986
+ * Assert the total triangle count is at most `max`.
987
+ * Perfect as a performance budget guard to prevent scene bloat.
988
+ *
989
+ * @example expect(r3f).toHaveTotalTriangleCountLessThan(100_000);
990
+ */
991
+ toHaveTotalTriangleCountLessThan(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, max: number, options?: MatcherOptions): Promise<{
992
+ pass: boolean;
993
+ message: () => string;
994
+ name: string;
995
+ expected: string;
996
+ actual: number;
997
+ }>;
998
+ /**
999
+ * Assert the camera position is close to the expected [x, y, z].
1000
+ *
1001
+ * @example expect(r3f).toHaveCameraPosition([0, 5, 10], 0.1);
1002
+ */
1003
+ toHaveCameraPosition(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, expected: [number, number, number], tolOpts?: number | Vec3Opts): Promise<{
1004
+ pass: boolean;
1005
+ message: () => string;
1006
+ name: string;
1007
+ expected: [number, number, number];
1008
+ actual: [number, number, number];
1009
+ }>;
1010
+ /**
1011
+ * Assert the camera field of view (PerspectiveCamera only).
1012
+ *
1013
+ * @example expect(r3f).toHaveCameraFov(75);
1014
+ */
1015
+ toHaveCameraFov(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, expected: number, tolOpts?: number | Vec3Opts): Promise<{
1016
+ pass: boolean;
1017
+ message: () => string;
1018
+ name: string;
1019
+ expected: number;
1020
+ actual: number | undefined;
1021
+ }>;
1022
+ /**
1023
+ * Assert the camera near clipping plane.
1024
+ *
1025
+ * @example expect(r3f).toHaveCameraNear(0.1);
1026
+ */
1027
+ toHaveCameraNear(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, expected: number, tolOpts?: number | Vec3Opts): Promise<{
1028
+ pass: boolean;
1029
+ message: () => string;
1030
+ name: string;
1031
+ expected: number;
1032
+ actual: number;
1033
+ }>;
1034
+ /**
1035
+ * Assert the camera far clipping plane.
1036
+ *
1037
+ * @example expect(r3f).toHaveCameraFar(1000);
1038
+ */
1039
+ toHaveCameraFar(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, expected: number, tolOpts?: number | Vec3Opts): Promise<{
1040
+ pass: boolean;
1041
+ message: () => string;
1042
+ name: string;
1043
+ expected: number;
1044
+ actual: number;
1045
+ }>;
1046
+ /**
1047
+ * Assert the camera zoom level.
1048
+ *
1049
+ * @example expect(r3f).toHaveCameraZoom(1);
1050
+ */
1051
+ toHaveCameraZoom(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, expected: number, tolOpts?: number | Vec3Opts): Promise<{
1052
+ pass: boolean;
1053
+ message: () => string;
1054
+ name: string;
1055
+ expected: number;
1056
+ actual: number;
1057
+ }>;
1058
+ /**
1059
+ * Assert that ALL given objects exist in the scene.
1060
+ * Accepts an array of testIds/uuids or a glob pattern (e.g. "wall-*").
1061
+ *
1062
+ * @example expect(r3f).toAllExist(['wall-1', 'wall-2', 'floor']);
1063
+ * @example expect(r3f).toAllExist('wall-*');
1064
+ */
1065
+ toAllExist(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, idsOrPattern: string[] | string, opts?: MatcherOptions): Promise<{
1066
+ pass: boolean;
1067
+ message: () => string;
1068
+ name: string;
1069
+ expected: string | string[];
1070
+ actual: {
1071
+ missing: string[];
1072
+ };
1073
+ }>;
1074
+ /**
1075
+ * Assert that ALL given objects are visible.
1076
+ *
1077
+ * @example expect(r3f).toAllBeVisible(['wall-1', 'wall-2', 'floor']);
1078
+ * @example expect(r3f).toAllBeVisible('wall-*');
1079
+ */
1080
+ toAllBeVisible(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, idsOrPattern: string[] | string, opts?: MatcherOptions): Promise<{
1081
+ pass: boolean;
1082
+ message: () => string;
1083
+ name: string;
1084
+ expected: string | string[];
1085
+ actual: {
1086
+ hidden: string[];
1087
+ };
1088
+ }>;
1089
+ /**
1090
+ * Assert that NONE of the given objects exist in the scene.
1091
+ *
1092
+ * @example expect(r3f).toNoneExist(['deleted-wall', 'old-floor']);
1093
+ * @example expect(r3f).toNoneExist('temp-*');
1094
+ */
1095
+ toNoneExist(this: ExpectMatcherContext, r3f: R3FMatcherReceiver, idsOrPattern: string[] | string, opts?: MatcherOptions): Promise<{
1096
+ pass: boolean;
1097
+ message: () => string;
1098
+ name: string;
1099
+ expected: string | string[];
1100
+ actual: {
1101
+ found: string[];
1102
+ };
1103
+ }>;
1104
+ };
1105
+
1106
+ /**
1107
+ * @module interactions
1108
+ *
1109
+ * Playwright interaction helpers — thin wrappers around `page.evaluate` calls
1110
+ * to the `window.__R3F_DOM__` bridge interaction methods.
1111
+ *
1112
+ * All object-targeted interactions auto-wait for:
1113
+ * 1. The bridge to be ready (`_ready === true`)
1114
+ * 2. The target object to exist (by testId or uuid)
1115
+ *
1116
+ * This mirrors Playwright's built-in auto-waiting on locators.
1117
+ *
1118
+ * Multi-canvas: pass `canvasId` to target a specific canvas instance.
1119
+ * When undefined, uses the default `window.__R3F_DOM__`.
1120
+ */
279
1121
 
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. */
1122
+ /** Click a 3D object by its testId or uuid. Auto-waits for the object. */
1123
+ declare function click(page: Page, idOrUuid: string, timeout?: number, canvasId?: string): Promise<void>;
1124
+ /** Double-click a 3D object by its testId or uuid. Auto-waits for the object. */
1125
+ declare function doubleClick(page: Page, idOrUuid: string, timeout?: number, canvasId?: string): Promise<void>;
1126
+ /** Right-click / context-menu a 3D object. Auto-waits for the object. */
1127
+ declare function contextMenu(page: Page, idOrUuid: string, timeout?: number, canvasId?: string): Promise<void>;
1128
+ /** Hover over a 3D object. Auto-waits for the object. */
1129
+ declare function hover(page: Page, idOrUuid: string, timeout?: number, canvasId?: string): Promise<void>;
1130
+ /** Unhover / pointer-leave resets hover state by moving pointer off-canvas. Auto-waits for bridge. */
1131
+ declare function unhover(page: Page, timeout?: number, canvasId?: string): Promise<void>;
1132
+ /** Drag a 3D object with a world-space delta. Auto-waits for the object. */
289
1133
  declare function drag(page: Page, idOrUuid: string, delta: {
290
1134
  x: number;
291
1135
  y: number;
292
1136
  z: number;
293
- }): Promise<void>;
294
- /** Dispatch a wheel/scroll event on a 3D object. */
1137
+ }, timeout?: number, canvasId?: string): Promise<void>;
1138
+ /** Dispatch a wheel/scroll event on a 3D object. Auto-waits for the object. */
295
1139
  declare function wheel(page: Page, idOrUuid: string, options?: {
296
1140
  deltaY?: number;
297
1141
  deltaX?: number;
298
- }): Promise<void>;
299
- /** Click empty space to trigger onPointerMissed handlers. */
300
- declare function pointerMiss(page: Page): Promise<void>;
1142
+ }, timeout?: number, canvasId?: string): Promise<void>;
1143
+ /** Click empty space to trigger onPointerMissed handlers. Auto-waits for bridge. */
1144
+ declare function pointerMiss(page: Page, timeout?: number, canvasId?: string): Promise<void>;
1145
+ /** Draw a freeform path on the canvas. Auto-waits for bridge. */
1146
+ declare function drawPathOnCanvas(page: Page, points: Array<{
1147
+ x: number;
1148
+ y: number;
1149
+ pressure?: number;
1150
+ }>, options?: {
1151
+ stepDelayMs?: number;
1152
+ pointerType?: 'mouse' | 'pen' | 'touch';
1153
+ clickAtEnd?: boolean;
1154
+ }, timeout?: number, canvasId?: string): Promise<{
1155
+ eventCount: number;
1156
+ pointCount: number;
1157
+ }>;
1158
+ /** Get the current camera state (position, rotation, fov, near, far, zoom). Auto-waits for bridge. */
1159
+ declare function getCameraState(page: Page, timeout?: number, canvasId?: string): Promise<CameraState>;
1160
+
1161
+ /**
1162
+ * @module pathGenerators
1163
+ *
1164
+ * Pure geometry utilities that generate arrays of screen-space points for
1165
+ * use with `r3f.drawPath()`. Provides line, quadratic bezier, rectangle,
1166
+ * and circle/ellipse path generators.
1167
+ *
1168
+ * Duplicated from `@react-three-dom/core` to avoid a runtime dependency.
1169
+ */
1170
+ /** A 2D screen-space point in CSS pixels, relative to the canvas top-left. */
1171
+ interface DrawPoint {
1172
+ /** X coordinate in CSS pixels from canvas left edge. */
1173
+ x: number;
1174
+ /** Y coordinate in CSS pixels from canvas top edge. */
1175
+ y: number;
1176
+ /** Optional pressure (0–1). Default: 0.5 */
1177
+ pressure?: number;
1178
+ }
1179
+ /**
1180
+ * Generate points along a straight line.
1181
+ *
1182
+ * @param start Start point
1183
+ * @param end End point
1184
+ * @param steps Intermediate points (excluding start/end). Default: 10
1185
+ * @param pressure Uniform pressure. Default: 0.5
1186
+ */
1187
+ declare function linePath(start: {
1188
+ x: number;
1189
+ y: number;
1190
+ }, end: {
1191
+ x: number;
1192
+ y: number;
1193
+ }, steps?: number, pressure?: number): DrawPoint[];
1194
+ /**
1195
+ * Generate points along a quadratic bezier curve.
1196
+ *
1197
+ * @param start Start point
1198
+ * @param control Control point
1199
+ * @param end End point
1200
+ * @param steps Number of steps. Default: 20
1201
+ * @param pressure Uniform pressure. Default: 0.5
1202
+ */
1203
+ declare function curvePath(start: {
1204
+ x: number;
1205
+ y: number;
1206
+ }, control: {
1207
+ x: number;
1208
+ y: number;
1209
+ }, end: {
1210
+ x: number;
1211
+ y: number;
1212
+ }, steps?: number, pressure?: number): DrawPoint[];
1213
+ /**
1214
+ * Generate points forming a rectangle.
1215
+ *
1216
+ * @param topLeft Top-left corner
1217
+ * @param bottomRight Bottom-right corner
1218
+ * @param pointsPerSide Points per side. Default: 5
1219
+ * @param pressure Uniform pressure. Default: 0.5
1220
+ */
1221
+ declare function rectPath(topLeft: {
1222
+ x: number;
1223
+ y: number;
1224
+ }, bottomRight: {
1225
+ x: number;
1226
+ y: number;
1227
+ }, pointsPerSide?: number, pressure?: number): DrawPoint[];
1228
+ /**
1229
+ * Generate points forming a circle/ellipse.
1230
+ *
1231
+ * @param center Center point
1232
+ * @param radiusX Horizontal radius in CSS pixels
1233
+ * @param radiusY Vertical radius. Default: same as radiusX
1234
+ * @param steps Number of points. Default: 36
1235
+ * @param pressure Uniform pressure. Default: 0.5
1236
+ */
1237
+ declare function circlePath(center: {
1238
+ x: number;
1239
+ y: number;
1240
+ }, radiusX: number, radiusY?: number, steps?: number, pressure?: number): DrawPoint[];
301
1241
 
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 };
1242
+ export { type BridgeDiagnostics, type CameraState, type DrawPoint, type InspectOptions, type ObjectInspection, type ObjectMetadata, R3FFixture, type R3FFixtureOptions, R3FReporter, type SceneDiff, type SceneDiffChange, type SceneSnapshot, type SnapshotNode, type WaitForIdleOptions, type WaitForNewObjectOptions, type WaitForNewObjectResult, type WaitForObjectOptions, type WaitForObjectRemovedOptions, type WaitForSceneReadyOptions, circlePath, click, contextMenu, createR3FTest, curvePath, diffSnapshots, doubleClick, drag, drawPathOnCanvas, getCameraState, hover, linePath, pointerMiss, r3fMatchers, rectPath, test, unhover, waitForIdle, waitForNewObject, waitForObject, waitForObjectRemoved, waitForSceneReady, wheel };