@react-three-dom/playwright 0.1.0 → 0.1.2

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.cjs CHANGED
@@ -32,6 +32,32 @@ async function waitForSceneReady(page, options = {}) {
32
32
  `waitForSceneReady timed out after ${timeout}ms. Last count: ${lastCount}, stable runs: ${stableRuns}/${stableChecks}`
33
33
  );
34
34
  }
35
+ async function waitForObject(page, idOrUuid, options = {}) {
36
+ const {
37
+ bridgeTimeout = 3e4,
38
+ objectTimeout = 4e4,
39
+ pollIntervalMs = 200
40
+ } = options;
41
+ await page.waitForFunction(() => typeof window.__R3F_DOM__ !== "undefined", void 0, {
42
+ timeout: bridgeTimeout
43
+ });
44
+ const deadline = Date.now() + objectTimeout;
45
+ while (Date.now() < deadline) {
46
+ const found = await page.evaluate(
47
+ (id) => {
48
+ const api = window.__R3F_DOM__;
49
+ if (!api) return false;
50
+ return (api.getByTestId(id) ?? api.getByUuid(id)) !== null;
51
+ },
52
+ idOrUuid
53
+ );
54
+ if (found) return;
55
+ await page.waitForTimeout(pollIntervalMs);
56
+ }
57
+ throw new Error(
58
+ `waitForObject("${idOrUuid}") timed out after ${objectTimeout}ms. Is the object rendered with userData.testId or this uuid?`
59
+ );
60
+ }
35
61
  async function waitForIdle(page, options = {}) {
36
62
  const {
37
63
  idleFrames = 10,
@@ -210,6 +236,14 @@ var R3FFixture = class {
210
236
  async waitForSceneReady(options) {
211
237
  return waitForSceneReady(this._page, options);
212
238
  }
239
+ /**
240
+ * Wait until the bridge is available and an object with the given testId or
241
+ * uuid exists. Use this instead of waitForSceneReady when the scene count
242
+ * never stabilizes (e.g. async model loading, continuous animations).
243
+ */
244
+ async waitForObject(idOrUuid, options) {
245
+ return waitForObject(this._page, idOrUuid, options);
246
+ }
213
247
  /**
214
248
  * Wait until no object properties have changed for a number of consecutive
215
249
  * animation frames. Useful after triggering interactions or animations.
@@ -385,6 +419,7 @@ exports.hover = hover;
385
419
  exports.pointerMiss = pointerMiss;
386
420
  exports.test = test;
387
421
  exports.waitForIdle = waitForIdle;
422
+ exports.waitForObject = waitForObject;
388
423
  exports.waitForSceneReady = waitForSceneReady;
389
424
  exports.wheel = wheel;
390
425
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/waiters.ts","../src/interactions.ts","../src/fixtures.ts","../src/assertions.ts"],"names":["base","baseExpect"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Page } from '@playwright/test';\n\n// ---------------------------------------------------------------------------\n// waitForSceneReady — wait until the scene object count stabilises\n// ---------------------------------------------------------------------------\n\nexport interface WaitForSceneReadyOptions {\n /** How many consecutive stable polls are required. Default: 3 */\n stableChecks?: number;\n /** Interval between polls in ms. Default: 100 */\n pollIntervalMs?: number;\n /** Overall timeout in ms. Default: 10_000 */\n timeout?: number;\n}\n\n/**\n * Wait until `window.__R3F_DOM__` is available and the scene's object count\n * has stabilised (no additions or removals) over several consecutive checks.\n */\nexport async function waitForSceneReady(\n page: Page,\n options: WaitForSceneReadyOptions = {},\n): Promise<void> {\n const {\n stableChecks = 3,\n pollIntervalMs = 100,\n timeout = 10_000,\n } = options;\n\n const deadline = Date.now() + timeout;\n\n // First, wait for __R3F_DOM__ to exist\n await page.waitForFunction(() => typeof window.__R3F_DOM__ !== 'undefined', undefined, {\n timeout,\n });\n\n let lastCount = -1;\n let stableRuns = 0;\n\n while (Date.now() < deadline) {\n const count = await page.evaluate(() => window.__R3F_DOM__!.getCount());\n\n if (count === lastCount && count > 0) {\n stableRuns++;\n if (stableRuns >= stableChecks) return;\n } else {\n stableRuns = 0;\n }\n\n lastCount = count;\n await page.waitForTimeout(pollIntervalMs);\n }\n\n throw new Error(\n `waitForSceneReady timed out after ${timeout}ms. Last count: ${lastCount}, stable runs: ${stableRuns}/${stableChecks}`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// waitForIdle — wait until no property changes for N consecutive frames\n// ---------------------------------------------------------------------------\n\nexport interface WaitForIdleOptions {\n /** Number of consecutive idle frames required. Default: 10 */\n idleFrames?: number;\n /** Overall timeout in ms. Default: 10_000 */\n timeout?: number;\n}\n\n/**\n * Wait until no object properties have changed for a number of consecutive\n * animation frames. Useful after triggering animations or interactions.\n *\n * This works by taking successive snapshots and comparing them. When the\n * JSON representation is unchanged for `idleFrames` consecutive rAF\n * callbacks, the scene is considered idle.\n */\nexport async function waitForIdle(\n page: Page,\n options: WaitForIdleOptions = {},\n): Promise<void> {\n const {\n idleFrames = 10,\n timeout = 10_000,\n } = options;\n\n const settled = await page.evaluate(\n ([frames, timeoutMs]) => {\n return new Promise<boolean>((resolve) => {\n const deadline = Date.now() + timeoutMs;\n let lastJson = '';\n let stableCount = 0;\n\n function check() {\n if (Date.now() > deadline) {\n resolve(false);\n return;\n }\n\n const snap = window.__R3F_DOM__?.snapshot();\n const json = snap ? JSON.stringify(snap.tree) : '';\n\n if (json === lastJson && json !== '') {\n stableCount++;\n if (stableCount >= frames) {\n resolve(true);\n return;\n }\n } else {\n stableCount = 0;\n }\n\n lastJson = json;\n requestAnimationFrame(check);\n }\n\n requestAnimationFrame(check);\n });\n },\n [idleFrames, timeout] as const,\n );\n\n if (!settled) {\n throw new Error(`waitForIdle timed out after ${timeout}ms`);\n }\n}\n","import type { Page } from '@playwright/test';\n\n// ---------------------------------------------------------------------------\n// Interaction helpers — thin wrappers around page.evaluate calls to\n// window.__R3F_DOM__ interaction methods.\n// ---------------------------------------------------------------------------\n\nfunction ensureBridge(page: Page): Page {\n // The bridge is guaranteed to exist after waitForSceneReady.\n // If users skip that, the evaluate calls will throw a clear error.\n return page;\n}\n\n/** Click a 3D object by its testId or uuid. */\nexport async function click(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.click(id);\n }, idOrUuid);\n}\n\n/** Double-click a 3D object by its testId or uuid. */\nexport async function doubleClick(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.doubleClick(id);\n }, idOrUuid);\n}\n\n/** Right-click / context-menu a 3D object by its testId or uuid. */\nexport async function contextMenu(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.contextMenu(id);\n }, idOrUuid);\n}\n\n/** Hover over a 3D object by its testId or uuid. */\nexport async function hover(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.hover(id);\n }, idOrUuid);\n}\n\n/** Drag a 3D object by its testId or uuid with a given world-space delta. */\nexport async function drag(\n page: Page,\n idOrUuid: string,\n delta: { x: number; y: number; z: number },\n): Promise<void> {\n ensureBridge(page);\n await page.evaluate(\n ([id, d]) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.drag(id, d);\n },\n [idOrUuid, delta] as const,\n );\n}\n\n/** Dispatch a wheel/scroll event on a 3D object. */\nexport async function wheel(\n page: Page,\n idOrUuid: string,\n options?: { deltaY?: number; deltaX?: number },\n): Promise<void> {\n ensureBridge(page);\n await page.evaluate(\n ([id, opts]) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.wheel(id, opts);\n },\n [idOrUuid, options] as const,\n );\n}\n\n/** Click empty space to trigger onPointerMissed handlers. */\nexport async function pointerMiss(page: Page): Promise<void> {\n ensureBridge(page);\n await page.evaluate(() => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.pointerMiss();\n });\n}\n","import { test as base } from '@playwright/test';\nimport type { Page } from '@playwright/test';\nimport type { ObjectMetadata, ObjectInspection, SceneSnapshot } from './types';\nimport { waitForSceneReady, waitForIdle } from './waiters';\nimport type { WaitForSceneReadyOptions, WaitForIdleOptions } from './waiters';\nimport * as interactions from './interactions';\n\n// ---------------------------------------------------------------------------\n// R3FFixture — the main API object provided to Playwright tests\n// ---------------------------------------------------------------------------\n\nexport class R3FFixture {\n constructor(private readonly _page: Page) {}\n\n /** The underlying Playwright Page. */\n get page(): Page {\n return this._page;\n }\n\n // -----------------------------------------------------------------------\n // Queries\n // -----------------------------------------------------------------------\n\n /** Get object metadata by testId or uuid. Returns null if not found. */\n async getObject(idOrUuid: string): Promise<ObjectMetadata | null> {\n return this._page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) return null;\n return api.getByTestId(id) ?? api.getByUuid(id) ?? null;\n }, idOrUuid);\n }\n\n /** Get heavy inspection data (Tier 2) by testId or uuid. */\n async inspect(idOrUuid: string): Promise<ObjectInspection | null> {\n return this._page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) return null;\n return api.inspect(id);\n }, idOrUuid);\n }\n\n /** Take a full scene snapshot. */\n async snapshot(): Promise<SceneSnapshot | null> {\n return this._page.evaluate(() => {\n const api = window.__R3F_DOM__;\n return api ? api.snapshot() : null;\n });\n }\n\n /** Get the total number of tracked objects. */\n async getCount(): Promise<number> {\n return this._page.evaluate(() => {\n const api = window.__R3F_DOM__;\n return api ? api.getCount() : 0;\n });\n }\n\n // -----------------------------------------------------------------------\n // Interactions\n // -----------------------------------------------------------------------\n\n /** Click a 3D object by testId or uuid. */\n async click(idOrUuid: string): Promise<void> {\n return interactions.click(this._page, idOrUuid);\n }\n\n /** Double-click a 3D object by testId or uuid. */\n async doubleClick(idOrUuid: string): Promise<void> {\n return interactions.doubleClick(this._page, idOrUuid);\n }\n\n /** Right-click / context-menu a 3D object by testId or uuid. */\n async contextMenu(idOrUuid: string): Promise<void> {\n return interactions.contextMenu(this._page, idOrUuid);\n }\n\n /** Hover over a 3D object by testId or uuid. */\n async hover(idOrUuid: string): Promise<void> {\n return interactions.hover(this._page, idOrUuid);\n }\n\n /** Drag a 3D object with a world-space delta vector. */\n async drag(\n idOrUuid: string,\n delta: { x: number; y: number; z: number },\n ): Promise<void> {\n return interactions.drag(this._page, idOrUuid, delta);\n }\n\n /** Dispatch a wheel/scroll event on a 3D object. */\n async wheel(\n idOrUuid: string,\n options?: { deltaY?: number; deltaX?: number },\n ): Promise<void> {\n return interactions.wheel(this._page, idOrUuid, options);\n }\n\n /** Click empty space to trigger onPointerMissed handlers. */\n async pointerMiss(): Promise<void> {\n return interactions.pointerMiss(this._page);\n }\n\n // -----------------------------------------------------------------------\n // Waiters\n // -----------------------------------------------------------------------\n\n /**\n * Wait until the scene is ready — `window.__R3F_DOM__` is available and\n * the object count has stabilised across several consecutive checks.\n */\n async waitForSceneReady(options?: WaitForSceneReadyOptions): Promise<void> {\n return waitForSceneReady(this._page, options);\n }\n\n /**\n * Wait until no object properties have changed for a number of consecutive\n * animation frames. Useful after triggering interactions or animations.\n */\n async waitForIdle(options?: WaitForIdleOptions): Promise<void> {\n return waitForIdle(this._page, options);\n }\n\n // -----------------------------------------------------------------------\n // Selection (for inspector integration)\n // -----------------------------------------------------------------------\n\n /** Select a 3D object by testId or uuid (highlights in scene). */\n async select(idOrUuid: string): Promise<void> {\n await this._page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found');\n api.select(id);\n }, idOrUuid);\n }\n\n /** Clear the current selection. */\n async clearSelection(): Promise<void> {\n await this._page.evaluate(() => {\n const api = window.__R3F_DOM__;\n if (api) api.clearSelection();\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// test.extend — add the `r3f` fixture to Playwright's test runner\n// ---------------------------------------------------------------------------\n\nexport const test = base.extend<{ r3f: R3FFixture }>({\n r3f: async ({ page }, use) => {\n const fixture = new R3FFixture(page);\n await use(fixture);\n },\n});\n","import { expect as baseExpect } from '@playwright/test';\nimport type { ObjectMetadata, ObjectInspection } from './types';\n\n// ---------------------------------------------------------------------------\n// Custom Playwright expect matchers for 3D scene testing\n// ---------------------------------------------------------------------------\n\n/**\n * Extend Playwright's `expect` with 3D-native matchers.\n *\n * Usage:\n * ```ts\n * import { test, expect } from '@react-three-dom/playwright';\n *\n * test('chair exists', async ({ page, r3f }) => {\n * await r3f.waitForSceneReady();\n * await expect(r3f).toExist('chair-primary');\n * });\n * ```\n */\nexport const expect = baseExpect.extend({\n // -----------------------------------------------------------------------\n // toExist — verify an object with the given testId/uuid exists in the scene\n // -----------------------------------------------------------------------\n async toExist(r3f: R3FMatcherReceiver, idOrUuid: string) {\n const meta = await r3f.getObject(idOrUuid);\n const pass = meta !== null;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT exist in the scene, but it does`\n : `Expected object \"${idOrUuid}\" to exist in the scene, but it was not found`,\n name: 'toExist',\n expected: idOrUuid,\n actual: meta,\n };\n },\n\n // -----------------------------------------------------------------------\n // toBeVisible — verify an object is visible (object.visible === true)\n // -----------------------------------------------------------------------\n async toBeVisible(r3f: R3FMatcherReceiver, idOrUuid: string) {\n const meta = await r3f.getObject(idOrUuid);\n if (!meta) {\n return {\n pass: false,\n message: () => `Expected object \"${idOrUuid}\" to be visible, but it was not found in the scene`,\n name: 'toBeVisible',\n };\n }\n\n const pass = meta.visible;\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT be visible, but it is`\n : `Expected object \"${idOrUuid}\" to be visible, but visible=${meta.visible}`,\n name: 'toBeVisible',\n expected: true,\n actual: meta.visible,\n };\n },\n\n // -----------------------------------------------------------------------\n // toBeInFrustum — verify an object's bounding box intersects the camera\n // frustum (i.e. it is potentially on-screen). Uses inspect() for bounds.\n // -----------------------------------------------------------------------\n async toBeInFrustum(r3f: R3FMatcherReceiver, idOrUuid: string) {\n const inspection = await r3f.inspect(idOrUuid);\n if (!inspection) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to be in frustum, but it was not found in the scene`,\n name: 'toBeInFrustum',\n };\n }\n\n // If the bridge can project it to screen, it's in the frustum.\n // We check by verifying bounds exist and are finite.\n const { bounds } = inspection;\n const isFinite = (v: number[]) => v.every(Number.isFinite);\n const pass = isFinite(bounds.min) && isFinite(bounds.max);\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT be in the camera frustum`\n : `Expected object \"${idOrUuid}\" to be in the camera frustum, but its bounds are invalid`,\n name: 'toBeInFrustum',\n expected: 'finite bounds',\n actual: bounds,\n };\n },\n\n // -----------------------------------------------------------------------\n // toHavePosition — verify object position within tolerance\n // -----------------------------------------------------------------------\n async toHavePosition(\n r3f: R3FMatcherReceiver,\n idOrUuid: string,\n expected: [number, number, number],\n tolerance = 0.01,\n ) {\n const meta = await r3f.getObject(idOrUuid);\n if (!meta) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to have position [${expected}], but it was not found`,\n name: 'toHavePosition',\n };\n }\n\n const [ex, ey, ez] = expected;\n const [ax, ay, az] = meta.position;\n const dx = Math.abs(ax - ex);\n const dy = Math.abs(ay - ey);\n const dz = Math.abs(az - ez);\n const pass = dx <= tolerance && dy <= tolerance && dz <= tolerance;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT be at position [${expected}] (±${tolerance})`\n : `Expected object \"${idOrUuid}\" to be at position [${expected}] (±${tolerance}), but it is at [${meta.position}] (delta: [${dx.toFixed(4)}, ${dy.toFixed(4)}, ${dz.toFixed(4)}])`,\n name: 'toHavePosition',\n expected,\n actual: meta.position,\n };\n },\n\n // -----------------------------------------------------------------------\n // toHaveBounds — verify object bounding box (world-space)\n // -----------------------------------------------------------------------\n async toHaveBounds(\n r3f: R3FMatcherReceiver,\n idOrUuid: string,\n expected: { min: [number, number, number]; max: [number, number, number] },\n tolerance = 0.1,\n ) {\n const inspection = await r3f.inspect(idOrUuid);\n if (!inspection) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to have specific bounds, but it was not found`,\n name: 'toHaveBounds',\n };\n }\n\n const { bounds } = inspection;\n const withinTolerance = (a: number[], b: number[]) =>\n a.every((v, i) => Math.abs(v - b[i]) <= tolerance);\n const pass = withinTolerance(bounds.min, expected.min) && withinTolerance(bounds.max, expected.max);\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT have bounds min:${JSON.stringify(expected.min)} max:${JSON.stringify(expected.max)}`\n : `Expected object \"${idOrUuid}\" to have bounds min:${JSON.stringify(expected.min)} max:${JSON.stringify(expected.max)}, but got min:${JSON.stringify(bounds.min)} max:${JSON.stringify(bounds.max)}`,\n name: 'toHaveBounds',\n expected,\n actual: bounds,\n };\n },\n\n // -----------------------------------------------------------------------\n // toHaveInstanceCount — verify InstancedMesh instance count\n // -----------------------------------------------------------------------\n async toHaveInstanceCount(r3f: R3FMatcherReceiver, idOrUuid: string, expectedCount: number) {\n const meta = await r3f.getObject(idOrUuid);\n if (!meta) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to have instance count ${expectedCount}, but it was not found`,\n name: 'toHaveInstanceCount',\n };\n }\n\n const actual = meta.instanceCount ?? 0;\n const pass = actual === expectedCount;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT have instance count ${expectedCount}`\n : `Expected object \"${idOrUuid}\" to have instance count ${expectedCount}, but it has ${actual}`,\n name: 'toHaveInstanceCount',\n expected: expectedCount,\n actual,\n };\n },\n});\n\n// ---------------------------------------------------------------------------\n// Helper type — the R3FFixture object that matchers receive\n// ---------------------------------------------------------------------------\n\ninterface R3FMatcherReceiver {\n getObject(idOrUuid: string): Promise<ObjectMetadata | null>;\n inspect(idOrUuid: string): Promise<ObjectInspection | null>;\n}\n"]}
1
+ {"version":3,"sources":["../src/waiters.ts","../src/interactions.ts","../src/fixtures.ts","../src/assertions.ts"],"names":["base","baseExpect"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Page } from '@playwright/test';\n\n// ---------------------------------------------------------------------------\n// waitForSceneReady — wait until the scene object count stabilises\n// ---------------------------------------------------------------------------\n\nexport interface WaitForSceneReadyOptions {\n /** How many consecutive stable polls are required. Default: 3 */\n stableChecks?: number;\n /** Interval between polls in ms. Default: 100 */\n pollIntervalMs?: number;\n /** Overall timeout in ms. Default: 10_000 */\n timeout?: number;\n}\n\n/**\n * Wait until `window.__R3F_DOM__` is available and the scene's object count\n * has stabilised (no additions or removals) over several consecutive checks.\n */\nexport async function waitForSceneReady(\n page: Page,\n options: WaitForSceneReadyOptions = {},\n): Promise<void> {\n const {\n stableChecks = 3,\n pollIntervalMs = 100,\n timeout = 10_000,\n } = options;\n\n const deadline = Date.now() + timeout;\n\n // First, wait for __R3F_DOM__ to exist\n await page.waitForFunction(() => typeof window.__R3F_DOM__ !== 'undefined', undefined, {\n timeout,\n });\n\n let lastCount = -1;\n let stableRuns = 0;\n\n while (Date.now() < deadline) {\n const count = await page.evaluate(() => window.__R3F_DOM__!.getCount());\n\n if (count === lastCount && count > 0) {\n stableRuns++;\n if (stableRuns >= stableChecks) return;\n } else {\n stableRuns = 0;\n }\n\n lastCount = count;\n await page.waitForTimeout(pollIntervalMs);\n }\n\n throw new Error(\n `waitForSceneReady timed out after ${timeout}ms. Last count: ${lastCount}, stable runs: ${stableRuns}/${stableChecks}`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// waitForObject — wait for bridge + a specific object (when count never stabilizes)\n// ---------------------------------------------------------------------------\n\nexport interface WaitForObjectOptions {\n /** Time to wait for the bridge to appear. Default: 30_000 */\n bridgeTimeout?: number;\n /** Time to wait for the object to appear after bridge is ready. Default: 40_000 */\n objectTimeout?: number;\n /** Poll interval in ms. Default: 200 */\n pollIntervalMs?: number;\n}\n\n/**\n * Wait until `window.__R3F_DOM__` is available and an object with the given\n * testId or uuid exists in the scene.\n *\n * Use this instead of `waitForSceneReady` when the scene object count never\n * stabilizes (e.g. continuous loading, animations adding/removing objects,\n * or GLTF/models loading asynchronously).\n */\nexport async function waitForObject(\n page: Page,\n idOrUuid: string,\n options: WaitForObjectOptions = {},\n): Promise<void> {\n const {\n bridgeTimeout = 30_000,\n objectTimeout = 40_000,\n pollIntervalMs = 200,\n } = options;\n\n await page.waitForFunction(() => typeof window.__R3F_DOM__ !== 'undefined', undefined, {\n timeout: bridgeTimeout,\n });\n\n const deadline = Date.now() + objectTimeout;\n while (Date.now() < deadline) {\n const found = await page.evaluate(\n (id) => {\n const api = window.__R3F_DOM__;\n if (!api) return false;\n return (api.getByTestId(id) ?? api.getByUuid(id)) !== null;\n },\n idOrUuid,\n );\n if (found) return;\n await page.waitForTimeout(pollIntervalMs);\n }\n\n throw new Error(\n `waitForObject(\"${idOrUuid}\") timed out after ${objectTimeout}ms. Is the object rendered with userData.testId or this uuid?`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// waitForIdle — wait until no property changes for N consecutive frames\n// ---------------------------------------------------------------------------\n\nexport interface WaitForIdleOptions {\n /** Number of consecutive idle frames required. Default: 10 */\n idleFrames?: number;\n /** Overall timeout in ms. Default: 10_000 */\n timeout?: number;\n}\n\n/**\n * Wait until no object properties have changed for a number of consecutive\n * animation frames. Useful after triggering animations or interactions.\n *\n * This works by taking successive snapshots and comparing them. When the\n * JSON representation is unchanged for `idleFrames` consecutive rAF\n * callbacks, the scene is considered idle.\n */\nexport async function waitForIdle(\n page: Page,\n options: WaitForIdleOptions = {},\n): Promise<void> {\n const {\n idleFrames = 10,\n timeout = 10_000,\n } = options;\n\n const settled = await page.evaluate(\n ([frames, timeoutMs]) => {\n return new Promise<boolean>((resolve) => {\n const deadline = Date.now() + timeoutMs;\n let lastJson = '';\n let stableCount = 0;\n\n function check() {\n if (Date.now() > deadline) {\n resolve(false);\n return;\n }\n\n const snap = window.__R3F_DOM__?.snapshot();\n const json = snap ? JSON.stringify(snap.tree) : '';\n\n if (json === lastJson && json !== '') {\n stableCount++;\n if (stableCount >= frames) {\n resolve(true);\n return;\n }\n } else {\n stableCount = 0;\n }\n\n lastJson = json;\n requestAnimationFrame(check);\n }\n\n requestAnimationFrame(check);\n });\n },\n [idleFrames, timeout] as const,\n );\n\n if (!settled) {\n throw new Error(`waitForIdle timed out after ${timeout}ms`);\n }\n}\n","import type { Page } from '@playwright/test';\n\n// ---------------------------------------------------------------------------\n// Interaction helpers — thin wrappers around page.evaluate calls to\n// window.__R3F_DOM__ interaction methods.\n// ---------------------------------------------------------------------------\n\nfunction ensureBridge(page: Page): Page {\n // The bridge is guaranteed to exist after waitForSceneReady.\n // If users skip that, the evaluate calls will throw a clear error.\n return page;\n}\n\n/** Click a 3D object by its testId or uuid. */\nexport async function click(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.click(id);\n }, idOrUuid);\n}\n\n/** Double-click a 3D object by its testId or uuid. */\nexport async function doubleClick(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.doubleClick(id);\n }, idOrUuid);\n}\n\n/** Right-click / context-menu a 3D object by its testId or uuid. */\nexport async function contextMenu(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.contextMenu(id);\n }, idOrUuid);\n}\n\n/** Hover over a 3D object by its testId or uuid. */\nexport async function hover(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.hover(id);\n }, idOrUuid);\n}\n\n/** Drag a 3D object by its testId or uuid with a given world-space delta. */\nexport async function drag(\n page: Page,\n idOrUuid: string,\n delta: { x: number; y: number; z: number },\n): Promise<void> {\n ensureBridge(page);\n await page.evaluate(\n ([id, d]) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.drag(id, d);\n },\n [idOrUuid, delta] as const,\n );\n}\n\n/** Dispatch a wheel/scroll event on a 3D object. */\nexport async function wheel(\n page: Page,\n idOrUuid: string,\n options?: { deltaY?: number; deltaX?: number },\n): Promise<void> {\n ensureBridge(page);\n await page.evaluate(\n ([id, opts]) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.wheel(id, opts);\n },\n [idOrUuid, options] as const,\n );\n}\n\n/** Click empty space to trigger onPointerMissed handlers. */\nexport async function pointerMiss(page: Page): Promise<void> {\n ensureBridge(page);\n await page.evaluate(() => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.pointerMiss();\n });\n}\n","import { test as base } from '@playwright/test';\nimport type { Page } from '@playwright/test';\nimport type { ObjectMetadata, ObjectInspection, SceneSnapshot } from './types';\nimport { waitForSceneReady, waitForIdle, waitForObject } from './waiters';\nimport type {\n WaitForSceneReadyOptions,\n WaitForIdleOptions,\n WaitForObjectOptions,\n} from './waiters';\nimport * as interactions from './interactions';\n\n// ---------------------------------------------------------------------------\n// R3FFixture — the main API object provided to Playwright tests\n// ---------------------------------------------------------------------------\n\nexport class R3FFixture {\n constructor(private readonly _page: Page) {}\n\n /** The underlying Playwright Page. */\n get page(): Page {\n return this._page;\n }\n\n // -----------------------------------------------------------------------\n // Queries\n // -----------------------------------------------------------------------\n\n /** Get object metadata by testId or uuid. Returns null if not found. */\n async getObject(idOrUuid: string): Promise<ObjectMetadata | null> {\n return this._page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) return null;\n return api.getByTestId(id) ?? api.getByUuid(id) ?? null;\n }, idOrUuid);\n }\n\n /** Get heavy inspection data (Tier 2) by testId or uuid. */\n async inspect(idOrUuid: string): Promise<ObjectInspection | null> {\n return this._page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) return null;\n return api.inspect(id);\n }, idOrUuid);\n }\n\n /** Take a full scene snapshot. */\n async snapshot(): Promise<SceneSnapshot | null> {\n return this._page.evaluate(() => {\n const api = window.__R3F_DOM__;\n return api ? api.snapshot() : null;\n });\n }\n\n /** Get the total number of tracked objects. */\n async getCount(): Promise<number> {\n return this._page.evaluate(() => {\n const api = window.__R3F_DOM__;\n return api ? api.getCount() : 0;\n });\n }\n\n // -----------------------------------------------------------------------\n // Interactions\n // -----------------------------------------------------------------------\n\n /** Click a 3D object by testId or uuid. */\n async click(idOrUuid: string): Promise<void> {\n return interactions.click(this._page, idOrUuid);\n }\n\n /** Double-click a 3D object by testId or uuid. */\n async doubleClick(idOrUuid: string): Promise<void> {\n return interactions.doubleClick(this._page, idOrUuid);\n }\n\n /** Right-click / context-menu a 3D object by testId or uuid. */\n async contextMenu(idOrUuid: string): Promise<void> {\n return interactions.contextMenu(this._page, idOrUuid);\n }\n\n /** Hover over a 3D object by testId or uuid. */\n async hover(idOrUuid: string): Promise<void> {\n return interactions.hover(this._page, idOrUuid);\n }\n\n /** Drag a 3D object with a world-space delta vector. */\n async drag(\n idOrUuid: string,\n delta: { x: number; y: number; z: number },\n ): Promise<void> {\n return interactions.drag(this._page, idOrUuid, delta);\n }\n\n /** Dispatch a wheel/scroll event on a 3D object. */\n async wheel(\n idOrUuid: string,\n options?: { deltaY?: number; deltaX?: number },\n ): Promise<void> {\n return interactions.wheel(this._page, idOrUuid, options);\n }\n\n /** Click empty space to trigger onPointerMissed handlers. */\n async pointerMiss(): Promise<void> {\n return interactions.pointerMiss(this._page);\n }\n\n // -----------------------------------------------------------------------\n // Waiters\n // -----------------------------------------------------------------------\n\n /**\n * Wait until the scene is ready — `window.__R3F_DOM__` is available and\n * the object count has stabilised across several consecutive checks.\n */\n async waitForSceneReady(options?: WaitForSceneReadyOptions): Promise<void> {\n return waitForSceneReady(this._page, options);\n }\n\n /**\n * Wait until the bridge is available and an object with the given testId or\n * uuid exists. Use this instead of waitForSceneReady when the scene count\n * never stabilizes (e.g. async model loading, continuous animations).\n */\n async waitForObject(\n idOrUuid: string,\n options?: WaitForObjectOptions,\n ): Promise<void> {\n return waitForObject(this._page, idOrUuid, options);\n }\n\n /**\n * Wait until no object properties have changed for a number of consecutive\n * animation frames. Useful after triggering interactions or animations.\n */\n async waitForIdle(options?: WaitForIdleOptions): Promise<void> {\n return waitForIdle(this._page, options);\n }\n\n // -----------------------------------------------------------------------\n // Selection (for inspector integration)\n // -----------------------------------------------------------------------\n\n /** Select a 3D object by testId or uuid (highlights in scene). */\n async select(idOrUuid: string): Promise<void> {\n await this._page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found');\n api.select(id);\n }, idOrUuid);\n }\n\n /** Clear the current selection. */\n async clearSelection(): Promise<void> {\n await this._page.evaluate(() => {\n const api = window.__R3F_DOM__;\n if (api) api.clearSelection();\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// test.extend — add the `r3f` fixture to Playwright's test runner\n// ---------------------------------------------------------------------------\n\nexport const test = base.extend<{ r3f: R3FFixture }>({\n r3f: async ({ page }, use) => {\n const fixture = new R3FFixture(page);\n await use(fixture);\n },\n});\n","import { expect as baseExpect } from '@playwright/test';\nimport type { ObjectMetadata, ObjectInspection } from './types';\n\n// ---------------------------------------------------------------------------\n// Custom Playwright expect matchers for 3D scene testing\n// ---------------------------------------------------------------------------\n\n/**\n * Extend Playwright's `expect` with 3D-native matchers.\n *\n * Usage:\n * ```ts\n * import { test, expect } from '@react-three-dom/playwright';\n *\n * test('chair exists', async ({ page, r3f }) => {\n * await r3f.waitForSceneReady();\n * await expect(r3f).toExist('chair-primary');\n * });\n * ```\n */\nexport const expect = baseExpect.extend({\n // -----------------------------------------------------------------------\n // toExist — verify an object with the given testId/uuid exists in the scene\n // -----------------------------------------------------------------------\n async toExist(r3f: R3FMatcherReceiver, idOrUuid: string) {\n const meta = await r3f.getObject(idOrUuid);\n const pass = meta !== null;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT exist in the scene, but it does`\n : `Expected object \"${idOrUuid}\" to exist in the scene, but it was not found`,\n name: 'toExist',\n expected: idOrUuid,\n actual: meta,\n };\n },\n\n // -----------------------------------------------------------------------\n // toBeVisible — verify an object is visible (object.visible === true)\n // -----------------------------------------------------------------------\n async toBeVisible(r3f: R3FMatcherReceiver, idOrUuid: string) {\n const meta = await r3f.getObject(idOrUuid);\n if (!meta) {\n return {\n pass: false,\n message: () => `Expected object \"${idOrUuid}\" to be visible, but it was not found in the scene`,\n name: 'toBeVisible',\n };\n }\n\n const pass = meta.visible;\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT be visible, but it is`\n : `Expected object \"${idOrUuid}\" to be visible, but visible=${meta.visible}`,\n name: 'toBeVisible',\n expected: true,\n actual: meta.visible,\n };\n },\n\n // -----------------------------------------------------------------------\n // toBeInFrustum — verify an object's bounding box intersects the camera\n // frustum (i.e. it is potentially on-screen). Uses inspect() for bounds.\n // -----------------------------------------------------------------------\n async toBeInFrustum(r3f: R3FMatcherReceiver, idOrUuid: string) {\n const inspection = await r3f.inspect(idOrUuid);\n if (!inspection) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to be in frustum, but it was not found in the scene`,\n name: 'toBeInFrustum',\n };\n }\n\n // If the bridge can project it to screen, it's in the frustum.\n // We check by verifying bounds exist and are finite.\n const { bounds } = inspection;\n const isFinite = (v: number[]) => v.every(Number.isFinite);\n const pass = isFinite(bounds.min) && isFinite(bounds.max);\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT be in the camera frustum`\n : `Expected object \"${idOrUuid}\" to be in the camera frustum, but its bounds are invalid`,\n name: 'toBeInFrustum',\n expected: 'finite bounds',\n actual: bounds,\n };\n },\n\n // -----------------------------------------------------------------------\n // toHavePosition — verify object position within tolerance\n // -----------------------------------------------------------------------\n async toHavePosition(\n r3f: R3FMatcherReceiver,\n idOrUuid: string,\n expected: [number, number, number],\n tolerance = 0.01,\n ) {\n const meta = await r3f.getObject(idOrUuid);\n if (!meta) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to have position [${expected}], but it was not found`,\n name: 'toHavePosition',\n };\n }\n\n const [ex, ey, ez] = expected;\n const [ax, ay, az] = meta.position;\n const dx = Math.abs(ax - ex);\n const dy = Math.abs(ay - ey);\n const dz = Math.abs(az - ez);\n const pass = dx <= tolerance && dy <= tolerance && dz <= tolerance;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT be at position [${expected}] (±${tolerance})`\n : `Expected object \"${idOrUuid}\" to be at position [${expected}] (±${tolerance}), but it is at [${meta.position}] (delta: [${dx.toFixed(4)}, ${dy.toFixed(4)}, ${dz.toFixed(4)}])`,\n name: 'toHavePosition',\n expected,\n actual: meta.position,\n };\n },\n\n // -----------------------------------------------------------------------\n // toHaveBounds — verify object bounding box (world-space)\n // -----------------------------------------------------------------------\n async toHaveBounds(\n r3f: R3FMatcherReceiver,\n idOrUuid: string,\n expected: { min: [number, number, number]; max: [number, number, number] },\n tolerance = 0.1,\n ) {\n const inspection = await r3f.inspect(idOrUuid);\n if (!inspection) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to have specific bounds, but it was not found`,\n name: 'toHaveBounds',\n };\n }\n\n const { bounds } = inspection;\n const withinTolerance = (a: number[], b: number[]) =>\n a.every((v, i) => Math.abs(v - b[i]) <= tolerance);\n const pass = withinTolerance(bounds.min, expected.min) && withinTolerance(bounds.max, expected.max);\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT have bounds min:${JSON.stringify(expected.min)} max:${JSON.stringify(expected.max)}`\n : `Expected object \"${idOrUuid}\" to have bounds min:${JSON.stringify(expected.min)} max:${JSON.stringify(expected.max)}, but got min:${JSON.stringify(bounds.min)} max:${JSON.stringify(bounds.max)}`,\n name: 'toHaveBounds',\n expected,\n actual: bounds,\n };\n },\n\n // -----------------------------------------------------------------------\n // toHaveInstanceCount — verify InstancedMesh instance count\n // -----------------------------------------------------------------------\n async toHaveInstanceCount(r3f: R3FMatcherReceiver, idOrUuid: string, expectedCount: number) {\n const meta = await r3f.getObject(idOrUuid);\n if (!meta) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to have instance count ${expectedCount}, but it was not found`,\n name: 'toHaveInstanceCount',\n };\n }\n\n const actual = meta.instanceCount ?? 0;\n const pass = actual === expectedCount;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT have instance count ${expectedCount}`\n : `Expected object \"${idOrUuid}\" to have instance count ${expectedCount}, but it has ${actual}`,\n name: 'toHaveInstanceCount',\n expected: expectedCount,\n actual,\n };\n },\n});\n\n// ---------------------------------------------------------------------------\n// Helper type — the R3FFixture object that matchers receive\n// ---------------------------------------------------------------------------\n\ninterface R3FMatcherReceiver {\n getObject(idOrUuid: string): Promise<ObjectMetadata | null>;\n inspect(idOrUuid: string): Promise<ObjectInspection | null>;\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -81,6 +81,23 @@ interface WaitForSceneReadyOptions {
81
81
  * has stabilised (no additions or removals) over several consecutive checks.
82
82
  */
83
83
  declare function waitForSceneReady(page: Page, options?: WaitForSceneReadyOptions): Promise<void>;
84
+ interface WaitForObjectOptions {
85
+ /** Time to wait for the bridge to appear. Default: 30_000 */
86
+ bridgeTimeout?: number;
87
+ /** Time to wait for the object to appear after bridge is ready. Default: 40_000 */
88
+ objectTimeout?: number;
89
+ /** Poll interval in ms. Default: 200 */
90
+ pollIntervalMs?: number;
91
+ }
92
+ /**
93
+ * Wait until `window.__R3F_DOM__` is available and an object with the given
94
+ * testId or uuid exists in the scene.
95
+ *
96
+ * Use this instead of `waitForSceneReady` when the scene object count never
97
+ * stabilizes (e.g. continuous loading, animations adding/removing objects,
98
+ * or GLTF/models loading asynchronously).
99
+ */
100
+ declare function waitForObject(page: Page, idOrUuid: string, options?: WaitForObjectOptions): Promise<void>;
84
101
  interface WaitForIdleOptions {
85
102
  /** Number of consecutive idle frames required. Default: 10 */
86
103
  idleFrames?: number;
@@ -136,6 +153,12 @@ declare class R3FFixture {
136
153
  * the object count has stabilised across several consecutive checks.
137
154
  */
138
155
  waitForSceneReady(options?: WaitForSceneReadyOptions): Promise<void>;
156
+ /**
157
+ * Wait until the bridge is available and an object with the given testId or
158
+ * uuid exists. Use this instead of waitForSceneReady when the scene count
159
+ * never stabilizes (e.g. async model loading, continuous animations).
160
+ */
161
+ waitForObject(idOrUuid: string, options?: WaitForObjectOptions): Promise<void>;
139
162
  /**
140
163
  * Wait until no object properties have changed for a number of consecutive
141
164
  * animation frames. Useful after triggering interactions or animations.
@@ -276,4 +299,4 @@ declare function wheel(page: Page, idOrUuid: string, options?: {
276
299
  /** Click empty space to trigger onPointerMissed handlers. */
277
300
  declare function pointerMiss(page: Page): Promise<void>;
278
301
 
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 };
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 };
package/dist/index.d.ts CHANGED
@@ -81,6 +81,23 @@ interface WaitForSceneReadyOptions {
81
81
  * has stabilised (no additions or removals) over several consecutive checks.
82
82
  */
83
83
  declare function waitForSceneReady(page: Page, options?: WaitForSceneReadyOptions): Promise<void>;
84
+ interface WaitForObjectOptions {
85
+ /** Time to wait for the bridge to appear. Default: 30_000 */
86
+ bridgeTimeout?: number;
87
+ /** Time to wait for the object to appear after bridge is ready. Default: 40_000 */
88
+ objectTimeout?: number;
89
+ /** Poll interval in ms. Default: 200 */
90
+ pollIntervalMs?: number;
91
+ }
92
+ /**
93
+ * Wait until `window.__R3F_DOM__` is available and an object with the given
94
+ * testId or uuid exists in the scene.
95
+ *
96
+ * Use this instead of `waitForSceneReady` when the scene object count never
97
+ * stabilizes (e.g. continuous loading, animations adding/removing objects,
98
+ * or GLTF/models loading asynchronously).
99
+ */
100
+ declare function waitForObject(page: Page, idOrUuid: string, options?: WaitForObjectOptions): Promise<void>;
84
101
  interface WaitForIdleOptions {
85
102
  /** Number of consecutive idle frames required. Default: 10 */
86
103
  idleFrames?: number;
@@ -136,6 +153,12 @@ declare class R3FFixture {
136
153
  * the object count has stabilised across several consecutive checks.
137
154
  */
138
155
  waitForSceneReady(options?: WaitForSceneReadyOptions): Promise<void>;
156
+ /**
157
+ * Wait until the bridge is available and an object with the given testId or
158
+ * uuid exists. Use this instead of waitForSceneReady when the scene count
159
+ * never stabilizes (e.g. async model loading, continuous animations).
160
+ */
161
+ waitForObject(idOrUuid: string, options?: WaitForObjectOptions): Promise<void>;
139
162
  /**
140
163
  * Wait until no object properties have changed for a number of consecutive
141
164
  * animation frames. Useful after triggering interactions or animations.
@@ -276,4 +299,4 @@ declare function wheel(page: Page, idOrUuid: string, options?: {
276
299
  /** Click empty space to trigger onPointerMissed handlers. */
277
300
  declare function pointerMiss(page: Page): Promise<void>;
278
301
 
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 };
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 };
package/dist/index.js CHANGED
@@ -30,6 +30,32 @@ async function waitForSceneReady(page, options = {}) {
30
30
  `waitForSceneReady timed out after ${timeout}ms. Last count: ${lastCount}, stable runs: ${stableRuns}/${stableChecks}`
31
31
  );
32
32
  }
33
+ async function waitForObject(page, idOrUuid, options = {}) {
34
+ const {
35
+ bridgeTimeout = 3e4,
36
+ objectTimeout = 4e4,
37
+ pollIntervalMs = 200
38
+ } = options;
39
+ await page.waitForFunction(() => typeof window.__R3F_DOM__ !== "undefined", void 0, {
40
+ timeout: bridgeTimeout
41
+ });
42
+ const deadline = Date.now() + objectTimeout;
43
+ while (Date.now() < deadline) {
44
+ const found = await page.evaluate(
45
+ (id) => {
46
+ const api = window.__R3F_DOM__;
47
+ if (!api) return false;
48
+ return (api.getByTestId(id) ?? api.getByUuid(id)) !== null;
49
+ },
50
+ idOrUuid
51
+ );
52
+ if (found) return;
53
+ await page.waitForTimeout(pollIntervalMs);
54
+ }
55
+ throw new Error(
56
+ `waitForObject("${idOrUuid}") timed out after ${objectTimeout}ms. Is the object rendered with userData.testId or this uuid?`
57
+ );
58
+ }
33
59
  async function waitForIdle(page, options = {}) {
34
60
  const {
35
61
  idleFrames = 10,
@@ -208,6 +234,14 @@ var R3FFixture = class {
208
234
  async waitForSceneReady(options) {
209
235
  return waitForSceneReady(this._page, options);
210
236
  }
237
+ /**
238
+ * Wait until the bridge is available and an object with the given testId or
239
+ * uuid exists. Use this instead of waitForSceneReady when the scene count
240
+ * never stabilizes (e.g. async model loading, continuous animations).
241
+ */
242
+ async waitForObject(idOrUuid, options) {
243
+ return waitForObject(this._page, idOrUuid, options);
244
+ }
211
245
  /**
212
246
  * Wait until no object properties have changed for a number of consecutive
213
247
  * animation frames. Useful after triggering interactions or animations.
@@ -373,6 +407,6 @@ var expect = expect$1.extend({
373
407
  }
374
408
  });
375
409
 
376
- export { R3FFixture, click, contextMenu, doubleClick, drag, expect, hover, pointerMiss, test, waitForIdle, waitForSceneReady, wheel };
410
+ export { R3FFixture, click, contextMenu, doubleClick, drag, expect, hover, pointerMiss, test, waitForIdle, waitForObject, waitForSceneReady, wheel };
377
411
  //# sourceMappingURL=index.js.map
378
412
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/waiters.ts","../src/interactions.ts","../src/fixtures.ts","../src/assertions.ts"],"names":["base","baseExpect"],"mappings":"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","file":"index.js","sourcesContent":["import type { Page } from '@playwright/test';\n\n// ---------------------------------------------------------------------------\n// waitForSceneReady — wait until the scene object count stabilises\n// ---------------------------------------------------------------------------\n\nexport interface WaitForSceneReadyOptions {\n /** How many consecutive stable polls are required. Default: 3 */\n stableChecks?: number;\n /** Interval between polls in ms. Default: 100 */\n pollIntervalMs?: number;\n /** Overall timeout in ms. Default: 10_000 */\n timeout?: number;\n}\n\n/**\n * Wait until `window.__R3F_DOM__` is available and the scene's object count\n * has stabilised (no additions or removals) over several consecutive checks.\n */\nexport async function waitForSceneReady(\n page: Page,\n options: WaitForSceneReadyOptions = {},\n): Promise<void> {\n const {\n stableChecks = 3,\n pollIntervalMs = 100,\n timeout = 10_000,\n } = options;\n\n const deadline = Date.now() + timeout;\n\n // First, wait for __R3F_DOM__ to exist\n await page.waitForFunction(() => typeof window.__R3F_DOM__ !== 'undefined', undefined, {\n timeout,\n });\n\n let lastCount = -1;\n let stableRuns = 0;\n\n while (Date.now() < deadline) {\n const count = await page.evaluate(() => window.__R3F_DOM__!.getCount());\n\n if (count === lastCount && count > 0) {\n stableRuns++;\n if (stableRuns >= stableChecks) return;\n } else {\n stableRuns = 0;\n }\n\n lastCount = count;\n await page.waitForTimeout(pollIntervalMs);\n }\n\n throw new Error(\n `waitForSceneReady timed out after ${timeout}ms. Last count: ${lastCount}, stable runs: ${stableRuns}/${stableChecks}`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// waitForIdle — wait until no property changes for N consecutive frames\n// ---------------------------------------------------------------------------\n\nexport interface WaitForIdleOptions {\n /** Number of consecutive idle frames required. Default: 10 */\n idleFrames?: number;\n /** Overall timeout in ms. Default: 10_000 */\n timeout?: number;\n}\n\n/**\n * Wait until no object properties have changed for a number of consecutive\n * animation frames. Useful after triggering animations or interactions.\n *\n * This works by taking successive snapshots and comparing them. When the\n * JSON representation is unchanged for `idleFrames` consecutive rAF\n * callbacks, the scene is considered idle.\n */\nexport async function waitForIdle(\n page: Page,\n options: WaitForIdleOptions = {},\n): Promise<void> {\n const {\n idleFrames = 10,\n timeout = 10_000,\n } = options;\n\n const settled = await page.evaluate(\n ([frames, timeoutMs]) => {\n return new Promise<boolean>((resolve) => {\n const deadline = Date.now() + timeoutMs;\n let lastJson = '';\n let stableCount = 0;\n\n function check() {\n if (Date.now() > deadline) {\n resolve(false);\n return;\n }\n\n const snap = window.__R3F_DOM__?.snapshot();\n const json = snap ? JSON.stringify(snap.tree) : '';\n\n if (json === lastJson && json !== '') {\n stableCount++;\n if (stableCount >= frames) {\n resolve(true);\n return;\n }\n } else {\n stableCount = 0;\n }\n\n lastJson = json;\n requestAnimationFrame(check);\n }\n\n requestAnimationFrame(check);\n });\n },\n [idleFrames, timeout] as const,\n );\n\n if (!settled) {\n throw new Error(`waitForIdle timed out after ${timeout}ms`);\n }\n}\n","import type { Page } from '@playwright/test';\n\n// ---------------------------------------------------------------------------\n// Interaction helpers — thin wrappers around page.evaluate calls to\n// window.__R3F_DOM__ interaction methods.\n// ---------------------------------------------------------------------------\n\nfunction ensureBridge(page: Page): Page {\n // The bridge is guaranteed to exist after waitForSceneReady.\n // If users skip that, the evaluate calls will throw a clear error.\n return page;\n}\n\n/** Click a 3D object by its testId or uuid. */\nexport async function click(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.click(id);\n }, idOrUuid);\n}\n\n/** Double-click a 3D object by its testId or uuid. */\nexport async function doubleClick(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.doubleClick(id);\n }, idOrUuid);\n}\n\n/** Right-click / context-menu a 3D object by its testId or uuid. */\nexport async function contextMenu(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.contextMenu(id);\n }, idOrUuid);\n}\n\n/** Hover over a 3D object by its testId or uuid. */\nexport async function hover(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.hover(id);\n }, idOrUuid);\n}\n\n/** Drag a 3D object by its testId or uuid with a given world-space delta. */\nexport async function drag(\n page: Page,\n idOrUuid: string,\n delta: { x: number; y: number; z: number },\n): Promise<void> {\n ensureBridge(page);\n await page.evaluate(\n ([id, d]) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.drag(id, d);\n },\n [idOrUuid, delta] as const,\n );\n}\n\n/** Dispatch a wheel/scroll event on a 3D object. */\nexport async function wheel(\n page: Page,\n idOrUuid: string,\n options?: { deltaY?: number; deltaX?: number },\n): Promise<void> {\n ensureBridge(page);\n await page.evaluate(\n ([id, opts]) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.wheel(id, opts);\n },\n [idOrUuid, options] as const,\n );\n}\n\n/** Click empty space to trigger onPointerMissed handlers. */\nexport async function pointerMiss(page: Page): Promise<void> {\n ensureBridge(page);\n await page.evaluate(() => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.pointerMiss();\n });\n}\n","import { test as base } from '@playwright/test';\nimport type { Page } from '@playwright/test';\nimport type { ObjectMetadata, ObjectInspection, SceneSnapshot } from './types';\nimport { waitForSceneReady, waitForIdle } from './waiters';\nimport type { WaitForSceneReadyOptions, WaitForIdleOptions } from './waiters';\nimport * as interactions from './interactions';\n\n// ---------------------------------------------------------------------------\n// R3FFixture — the main API object provided to Playwright tests\n// ---------------------------------------------------------------------------\n\nexport class R3FFixture {\n constructor(private readonly _page: Page) {}\n\n /** The underlying Playwright Page. */\n get page(): Page {\n return this._page;\n }\n\n // -----------------------------------------------------------------------\n // Queries\n // -----------------------------------------------------------------------\n\n /** Get object metadata by testId or uuid. Returns null if not found. */\n async getObject(idOrUuid: string): Promise<ObjectMetadata | null> {\n return this._page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) return null;\n return api.getByTestId(id) ?? api.getByUuid(id) ?? null;\n }, idOrUuid);\n }\n\n /** Get heavy inspection data (Tier 2) by testId or uuid. */\n async inspect(idOrUuid: string): Promise<ObjectInspection | null> {\n return this._page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) return null;\n return api.inspect(id);\n }, idOrUuid);\n }\n\n /** Take a full scene snapshot. */\n async snapshot(): Promise<SceneSnapshot | null> {\n return this._page.evaluate(() => {\n const api = window.__R3F_DOM__;\n return api ? api.snapshot() : null;\n });\n }\n\n /** Get the total number of tracked objects. */\n async getCount(): Promise<number> {\n return this._page.evaluate(() => {\n const api = window.__R3F_DOM__;\n return api ? api.getCount() : 0;\n });\n }\n\n // -----------------------------------------------------------------------\n // Interactions\n // -----------------------------------------------------------------------\n\n /** Click a 3D object by testId or uuid. */\n async click(idOrUuid: string): Promise<void> {\n return interactions.click(this._page, idOrUuid);\n }\n\n /** Double-click a 3D object by testId or uuid. */\n async doubleClick(idOrUuid: string): Promise<void> {\n return interactions.doubleClick(this._page, idOrUuid);\n }\n\n /** Right-click / context-menu a 3D object by testId or uuid. */\n async contextMenu(idOrUuid: string): Promise<void> {\n return interactions.contextMenu(this._page, idOrUuid);\n }\n\n /** Hover over a 3D object by testId or uuid. */\n async hover(idOrUuid: string): Promise<void> {\n return interactions.hover(this._page, idOrUuid);\n }\n\n /** Drag a 3D object with a world-space delta vector. */\n async drag(\n idOrUuid: string,\n delta: { x: number; y: number; z: number },\n ): Promise<void> {\n return interactions.drag(this._page, idOrUuid, delta);\n }\n\n /** Dispatch a wheel/scroll event on a 3D object. */\n async wheel(\n idOrUuid: string,\n options?: { deltaY?: number; deltaX?: number },\n ): Promise<void> {\n return interactions.wheel(this._page, idOrUuid, options);\n }\n\n /** Click empty space to trigger onPointerMissed handlers. */\n async pointerMiss(): Promise<void> {\n return interactions.pointerMiss(this._page);\n }\n\n // -----------------------------------------------------------------------\n // Waiters\n // -----------------------------------------------------------------------\n\n /**\n * Wait until the scene is ready — `window.__R3F_DOM__` is available and\n * the object count has stabilised across several consecutive checks.\n */\n async waitForSceneReady(options?: WaitForSceneReadyOptions): Promise<void> {\n return waitForSceneReady(this._page, options);\n }\n\n /**\n * Wait until no object properties have changed for a number of consecutive\n * animation frames. Useful after triggering interactions or animations.\n */\n async waitForIdle(options?: WaitForIdleOptions): Promise<void> {\n return waitForIdle(this._page, options);\n }\n\n // -----------------------------------------------------------------------\n // Selection (for inspector integration)\n // -----------------------------------------------------------------------\n\n /** Select a 3D object by testId or uuid (highlights in scene). */\n async select(idOrUuid: string): Promise<void> {\n await this._page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found');\n api.select(id);\n }, idOrUuid);\n }\n\n /** Clear the current selection. */\n async clearSelection(): Promise<void> {\n await this._page.evaluate(() => {\n const api = window.__R3F_DOM__;\n if (api) api.clearSelection();\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// test.extend — add the `r3f` fixture to Playwright's test runner\n// ---------------------------------------------------------------------------\n\nexport const test = base.extend<{ r3f: R3FFixture }>({\n r3f: async ({ page }, use) => {\n const fixture = new R3FFixture(page);\n await use(fixture);\n },\n});\n","import { expect as baseExpect } from '@playwright/test';\nimport type { ObjectMetadata, ObjectInspection } from './types';\n\n// ---------------------------------------------------------------------------\n// Custom Playwright expect matchers for 3D scene testing\n// ---------------------------------------------------------------------------\n\n/**\n * Extend Playwright's `expect` with 3D-native matchers.\n *\n * Usage:\n * ```ts\n * import { test, expect } from '@react-three-dom/playwright';\n *\n * test('chair exists', async ({ page, r3f }) => {\n * await r3f.waitForSceneReady();\n * await expect(r3f).toExist('chair-primary');\n * });\n * ```\n */\nexport const expect = baseExpect.extend({\n // -----------------------------------------------------------------------\n // toExist — verify an object with the given testId/uuid exists in the scene\n // -----------------------------------------------------------------------\n async toExist(r3f: R3FMatcherReceiver, idOrUuid: string) {\n const meta = await r3f.getObject(idOrUuid);\n const pass = meta !== null;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT exist in the scene, but it does`\n : `Expected object \"${idOrUuid}\" to exist in the scene, but it was not found`,\n name: 'toExist',\n expected: idOrUuid,\n actual: meta,\n };\n },\n\n // -----------------------------------------------------------------------\n // toBeVisible — verify an object is visible (object.visible === true)\n // -----------------------------------------------------------------------\n async toBeVisible(r3f: R3FMatcherReceiver, idOrUuid: string) {\n const meta = await r3f.getObject(idOrUuid);\n if (!meta) {\n return {\n pass: false,\n message: () => `Expected object \"${idOrUuid}\" to be visible, but it was not found in the scene`,\n name: 'toBeVisible',\n };\n }\n\n const pass = meta.visible;\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT be visible, but it is`\n : `Expected object \"${idOrUuid}\" to be visible, but visible=${meta.visible}`,\n name: 'toBeVisible',\n expected: true,\n actual: meta.visible,\n };\n },\n\n // -----------------------------------------------------------------------\n // toBeInFrustum — verify an object's bounding box intersects the camera\n // frustum (i.e. it is potentially on-screen). Uses inspect() for bounds.\n // -----------------------------------------------------------------------\n async toBeInFrustum(r3f: R3FMatcherReceiver, idOrUuid: string) {\n const inspection = await r3f.inspect(idOrUuid);\n if (!inspection) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to be in frustum, but it was not found in the scene`,\n name: 'toBeInFrustum',\n };\n }\n\n // If the bridge can project it to screen, it's in the frustum.\n // We check by verifying bounds exist and are finite.\n const { bounds } = inspection;\n const isFinite = (v: number[]) => v.every(Number.isFinite);\n const pass = isFinite(bounds.min) && isFinite(bounds.max);\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT be in the camera frustum`\n : `Expected object \"${idOrUuid}\" to be in the camera frustum, but its bounds are invalid`,\n name: 'toBeInFrustum',\n expected: 'finite bounds',\n actual: bounds,\n };\n },\n\n // -----------------------------------------------------------------------\n // toHavePosition — verify object position within tolerance\n // -----------------------------------------------------------------------\n async toHavePosition(\n r3f: R3FMatcherReceiver,\n idOrUuid: string,\n expected: [number, number, number],\n tolerance = 0.01,\n ) {\n const meta = await r3f.getObject(idOrUuid);\n if (!meta) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to have position [${expected}], but it was not found`,\n name: 'toHavePosition',\n };\n }\n\n const [ex, ey, ez] = expected;\n const [ax, ay, az] = meta.position;\n const dx = Math.abs(ax - ex);\n const dy = Math.abs(ay - ey);\n const dz = Math.abs(az - ez);\n const pass = dx <= tolerance && dy <= tolerance && dz <= tolerance;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT be at position [${expected}] (±${tolerance})`\n : `Expected object \"${idOrUuid}\" to be at position [${expected}] (±${tolerance}), but it is at [${meta.position}] (delta: [${dx.toFixed(4)}, ${dy.toFixed(4)}, ${dz.toFixed(4)}])`,\n name: 'toHavePosition',\n expected,\n actual: meta.position,\n };\n },\n\n // -----------------------------------------------------------------------\n // toHaveBounds — verify object bounding box (world-space)\n // -----------------------------------------------------------------------\n async toHaveBounds(\n r3f: R3FMatcherReceiver,\n idOrUuid: string,\n expected: { min: [number, number, number]; max: [number, number, number] },\n tolerance = 0.1,\n ) {\n const inspection = await r3f.inspect(idOrUuid);\n if (!inspection) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to have specific bounds, but it was not found`,\n name: 'toHaveBounds',\n };\n }\n\n const { bounds } = inspection;\n const withinTolerance = (a: number[], b: number[]) =>\n a.every((v, i) => Math.abs(v - b[i]) <= tolerance);\n const pass = withinTolerance(bounds.min, expected.min) && withinTolerance(bounds.max, expected.max);\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT have bounds min:${JSON.stringify(expected.min)} max:${JSON.stringify(expected.max)}`\n : `Expected object \"${idOrUuid}\" to have bounds min:${JSON.stringify(expected.min)} max:${JSON.stringify(expected.max)}, but got min:${JSON.stringify(bounds.min)} max:${JSON.stringify(bounds.max)}`,\n name: 'toHaveBounds',\n expected,\n actual: bounds,\n };\n },\n\n // -----------------------------------------------------------------------\n // toHaveInstanceCount — verify InstancedMesh instance count\n // -----------------------------------------------------------------------\n async toHaveInstanceCount(r3f: R3FMatcherReceiver, idOrUuid: string, expectedCount: number) {\n const meta = await r3f.getObject(idOrUuid);\n if (!meta) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to have instance count ${expectedCount}, but it was not found`,\n name: 'toHaveInstanceCount',\n };\n }\n\n const actual = meta.instanceCount ?? 0;\n const pass = actual === expectedCount;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT have instance count ${expectedCount}`\n : `Expected object \"${idOrUuid}\" to have instance count ${expectedCount}, but it has ${actual}`,\n name: 'toHaveInstanceCount',\n expected: expectedCount,\n actual,\n };\n },\n});\n\n// ---------------------------------------------------------------------------\n// Helper type — the R3FFixture object that matchers receive\n// ---------------------------------------------------------------------------\n\ninterface R3FMatcherReceiver {\n getObject(idOrUuid: string): Promise<ObjectMetadata | null>;\n inspect(idOrUuid: string): Promise<ObjectInspection | null>;\n}\n"]}
1
+ {"version":3,"sources":["../src/waiters.ts","../src/interactions.ts","../src/fixtures.ts","../src/assertions.ts"],"names":["base","baseExpect"],"mappings":"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","file":"index.js","sourcesContent":["import type { Page } from '@playwright/test';\n\n// ---------------------------------------------------------------------------\n// waitForSceneReady — wait until the scene object count stabilises\n// ---------------------------------------------------------------------------\n\nexport interface WaitForSceneReadyOptions {\n /** How many consecutive stable polls are required. Default: 3 */\n stableChecks?: number;\n /** Interval between polls in ms. Default: 100 */\n pollIntervalMs?: number;\n /** Overall timeout in ms. Default: 10_000 */\n timeout?: number;\n}\n\n/**\n * Wait until `window.__R3F_DOM__` is available and the scene's object count\n * has stabilised (no additions or removals) over several consecutive checks.\n */\nexport async function waitForSceneReady(\n page: Page,\n options: WaitForSceneReadyOptions = {},\n): Promise<void> {\n const {\n stableChecks = 3,\n pollIntervalMs = 100,\n timeout = 10_000,\n } = options;\n\n const deadline = Date.now() + timeout;\n\n // First, wait for __R3F_DOM__ to exist\n await page.waitForFunction(() => typeof window.__R3F_DOM__ !== 'undefined', undefined, {\n timeout,\n });\n\n let lastCount = -1;\n let stableRuns = 0;\n\n while (Date.now() < deadline) {\n const count = await page.evaluate(() => window.__R3F_DOM__!.getCount());\n\n if (count === lastCount && count > 0) {\n stableRuns++;\n if (stableRuns >= stableChecks) return;\n } else {\n stableRuns = 0;\n }\n\n lastCount = count;\n await page.waitForTimeout(pollIntervalMs);\n }\n\n throw new Error(\n `waitForSceneReady timed out after ${timeout}ms. Last count: ${lastCount}, stable runs: ${stableRuns}/${stableChecks}`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// waitForObject — wait for bridge + a specific object (when count never stabilizes)\n// ---------------------------------------------------------------------------\n\nexport interface WaitForObjectOptions {\n /** Time to wait for the bridge to appear. Default: 30_000 */\n bridgeTimeout?: number;\n /** Time to wait for the object to appear after bridge is ready. Default: 40_000 */\n objectTimeout?: number;\n /** Poll interval in ms. Default: 200 */\n pollIntervalMs?: number;\n}\n\n/**\n * Wait until `window.__R3F_DOM__` is available and an object with the given\n * testId or uuid exists in the scene.\n *\n * Use this instead of `waitForSceneReady` when the scene object count never\n * stabilizes (e.g. continuous loading, animations adding/removing objects,\n * or GLTF/models loading asynchronously).\n */\nexport async function waitForObject(\n page: Page,\n idOrUuid: string,\n options: WaitForObjectOptions = {},\n): Promise<void> {\n const {\n bridgeTimeout = 30_000,\n objectTimeout = 40_000,\n pollIntervalMs = 200,\n } = options;\n\n await page.waitForFunction(() => typeof window.__R3F_DOM__ !== 'undefined', undefined, {\n timeout: bridgeTimeout,\n });\n\n const deadline = Date.now() + objectTimeout;\n while (Date.now() < deadline) {\n const found = await page.evaluate(\n (id) => {\n const api = window.__R3F_DOM__;\n if (!api) return false;\n return (api.getByTestId(id) ?? api.getByUuid(id)) !== null;\n },\n idOrUuid,\n );\n if (found) return;\n await page.waitForTimeout(pollIntervalMs);\n }\n\n throw new Error(\n `waitForObject(\"${idOrUuid}\") timed out after ${objectTimeout}ms. Is the object rendered with userData.testId or this uuid?`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// waitForIdle — wait until no property changes for N consecutive frames\n// ---------------------------------------------------------------------------\n\nexport interface WaitForIdleOptions {\n /** Number of consecutive idle frames required. Default: 10 */\n idleFrames?: number;\n /** Overall timeout in ms. Default: 10_000 */\n timeout?: number;\n}\n\n/**\n * Wait until no object properties have changed for a number of consecutive\n * animation frames. Useful after triggering animations or interactions.\n *\n * This works by taking successive snapshots and comparing them. When the\n * JSON representation is unchanged for `idleFrames` consecutive rAF\n * callbacks, the scene is considered idle.\n */\nexport async function waitForIdle(\n page: Page,\n options: WaitForIdleOptions = {},\n): Promise<void> {\n const {\n idleFrames = 10,\n timeout = 10_000,\n } = options;\n\n const settled = await page.evaluate(\n ([frames, timeoutMs]) => {\n return new Promise<boolean>((resolve) => {\n const deadline = Date.now() + timeoutMs;\n let lastJson = '';\n let stableCount = 0;\n\n function check() {\n if (Date.now() > deadline) {\n resolve(false);\n return;\n }\n\n const snap = window.__R3F_DOM__?.snapshot();\n const json = snap ? JSON.stringify(snap.tree) : '';\n\n if (json === lastJson && json !== '') {\n stableCount++;\n if (stableCount >= frames) {\n resolve(true);\n return;\n }\n } else {\n stableCount = 0;\n }\n\n lastJson = json;\n requestAnimationFrame(check);\n }\n\n requestAnimationFrame(check);\n });\n },\n [idleFrames, timeout] as const,\n );\n\n if (!settled) {\n throw new Error(`waitForIdle timed out after ${timeout}ms`);\n }\n}\n","import type { Page } from '@playwright/test';\n\n// ---------------------------------------------------------------------------\n// Interaction helpers — thin wrappers around page.evaluate calls to\n// window.__R3F_DOM__ interaction methods.\n// ---------------------------------------------------------------------------\n\nfunction ensureBridge(page: Page): Page {\n // The bridge is guaranteed to exist after waitForSceneReady.\n // If users skip that, the evaluate calls will throw a clear error.\n return page;\n}\n\n/** Click a 3D object by its testId or uuid. */\nexport async function click(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.click(id);\n }, idOrUuid);\n}\n\n/** Double-click a 3D object by its testId or uuid. */\nexport async function doubleClick(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.doubleClick(id);\n }, idOrUuid);\n}\n\n/** Right-click / context-menu a 3D object by its testId or uuid. */\nexport async function contextMenu(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.contextMenu(id);\n }, idOrUuid);\n}\n\n/** Hover over a 3D object by its testId or uuid. */\nexport async function hover(page: Page, idOrUuid: string): Promise<void> {\n ensureBridge(page);\n await page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.hover(id);\n }, idOrUuid);\n}\n\n/** Drag a 3D object by its testId or uuid with a given world-space delta. */\nexport async function drag(\n page: Page,\n idOrUuid: string,\n delta: { x: number; y: number; z: number },\n): Promise<void> {\n ensureBridge(page);\n await page.evaluate(\n ([id, d]) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.drag(id, d);\n },\n [idOrUuid, delta] as const,\n );\n}\n\n/** Dispatch a wheel/scroll event on a 3D object. */\nexport async function wheel(\n page: Page,\n idOrUuid: string,\n options?: { deltaY?: number; deltaX?: number },\n): Promise<void> {\n ensureBridge(page);\n await page.evaluate(\n ([id, opts]) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.wheel(id, opts);\n },\n [idOrUuid, options] as const,\n );\n}\n\n/** Click empty space to trigger onPointerMissed handlers. */\nexport async function pointerMiss(page: Page): Promise<void> {\n ensureBridge(page);\n await page.evaluate(() => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n api.pointerMiss();\n });\n}\n","import { test as base } from '@playwright/test';\nimport type { Page } from '@playwright/test';\nimport type { ObjectMetadata, ObjectInspection, SceneSnapshot } from './types';\nimport { waitForSceneReady, waitForIdle, waitForObject } from './waiters';\nimport type {\n WaitForSceneReadyOptions,\n WaitForIdleOptions,\n WaitForObjectOptions,\n} from './waiters';\nimport * as interactions from './interactions';\n\n// ---------------------------------------------------------------------------\n// R3FFixture — the main API object provided to Playwright tests\n// ---------------------------------------------------------------------------\n\nexport class R3FFixture {\n constructor(private readonly _page: Page) {}\n\n /** The underlying Playwright Page. */\n get page(): Page {\n return this._page;\n }\n\n // -----------------------------------------------------------------------\n // Queries\n // -----------------------------------------------------------------------\n\n /** Get object metadata by testId or uuid. Returns null if not found. */\n async getObject(idOrUuid: string): Promise<ObjectMetadata | null> {\n return this._page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) return null;\n return api.getByTestId(id) ?? api.getByUuid(id) ?? null;\n }, idOrUuid);\n }\n\n /** Get heavy inspection data (Tier 2) by testId or uuid. */\n async inspect(idOrUuid: string): Promise<ObjectInspection | null> {\n return this._page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) return null;\n return api.inspect(id);\n }, idOrUuid);\n }\n\n /** Take a full scene snapshot. */\n async snapshot(): Promise<SceneSnapshot | null> {\n return this._page.evaluate(() => {\n const api = window.__R3F_DOM__;\n return api ? api.snapshot() : null;\n });\n }\n\n /** Get the total number of tracked objects. */\n async getCount(): Promise<number> {\n return this._page.evaluate(() => {\n const api = window.__R3F_DOM__;\n return api ? api.getCount() : 0;\n });\n }\n\n // -----------------------------------------------------------------------\n // Interactions\n // -----------------------------------------------------------------------\n\n /** Click a 3D object by testId or uuid. */\n async click(idOrUuid: string): Promise<void> {\n return interactions.click(this._page, idOrUuid);\n }\n\n /** Double-click a 3D object by testId or uuid. */\n async doubleClick(idOrUuid: string): Promise<void> {\n return interactions.doubleClick(this._page, idOrUuid);\n }\n\n /** Right-click / context-menu a 3D object by testId or uuid. */\n async contextMenu(idOrUuid: string): Promise<void> {\n return interactions.contextMenu(this._page, idOrUuid);\n }\n\n /** Hover over a 3D object by testId or uuid. */\n async hover(idOrUuid: string): Promise<void> {\n return interactions.hover(this._page, idOrUuid);\n }\n\n /** Drag a 3D object with a world-space delta vector. */\n async drag(\n idOrUuid: string,\n delta: { x: number; y: number; z: number },\n ): Promise<void> {\n return interactions.drag(this._page, idOrUuid, delta);\n }\n\n /** Dispatch a wheel/scroll event on a 3D object. */\n async wheel(\n idOrUuid: string,\n options?: { deltaY?: number; deltaX?: number },\n ): Promise<void> {\n return interactions.wheel(this._page, idOrUuid, options);\n }\n\n /** Click empty space to trigger onPointerMissed handlers. */\n async pointerMiss(): Promise<void> {\n return interactions.pointerMiss(this._page);\n }\n\n // -----------------------------------------------------------------------\n // Waiters\n // -----------------------------------------------------------------------\n\n /**\n * Wait until the scene is ready — `window.__R3F_DOM__` is available and\n * the object count has stabilised across several consecutive checks.\n */\n async waitForSceneReady(options?: WaitForSceneReadyOptions): Promise<void> {\n return waitForSceneReady(this._page, options);\n }\n\n /**\n * Wait until the bridge is available and an object with the given testId or\n * uuid exists. Use this instead of waitForSceneReady when the scene count\n * never stabilizes (e.g. async model loading, continuous animations).\n */\n async waitForObject(\n idOrUuid: string,\n options?: WaitForObjectOptions,\n ): Promise<void> {\n return waitForObject(this._page, idOrUuid, options);\n }\n\n /**\n * Wait until no object properties have changed for a number of consecutive\n * animation frames. Useful after triggering interactions or animations.\n */\n async waitForIdle(options?: WaitForIdleOptions): Promise<void> {\n return waitForIdle(this._page, options);\n }\n\n // -----------------------------------------------------------------------\n // Selection (for inspector integration)\n // -----------------------------------------------------------------------\n\n /** Select a 3D object by testId or uuid (highlights in scene). */\n async select(idOrUuid: string): Promise<void> {\n await this._page.evaluate((id) => {\n const api = window.__R3F_DOM__;\n if (!api) throw new Error('react-three-dom bridge not found');\n api.select(id);\n }, idOrUuid);\n }\n\n /** Clear the current selection. */\n async clearSelection(): Promise<void> {\n await this._page.evaluate(() => {\n const api = window.__R3F_DOM__;\n if (api) api.clearSelection();\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// test.extend — add the `r3f` fixture to Playwright's test runner\n// ---------------------------------------------------------------------------\n\nexport const test = base.extend<{ r3f: R3FFixture }>({\n r3f: async ({ page }, use) => {\n const fixture = new R3FFixture(page);\n await use(fixture);\n },\n});\n","import { expect as baseExpect } from '@playwright/test';\nimport type { ObjectMetadata, ObjectInspection } from './types';\n\n// ---------------------------------------------------------------------------\n// Custom Playwright expect matchers for 3D scene testing\n// ---------------------------------------------------------------------------\n\n/**\n * Extend Playwright's `expect` with 3D-native matchers.\n *\n * Usage:\n * ```ts\n * import { test, expect } from '@react-three-dom/playwright';\n *\n * test('chair exists', async ({ page, r3f }) => {\n * await r3f.waitForSceneReady();\n * await expect(r3f).toExist('chair-primary');\n * });\n * ```\n */\nexport const expect = baseExpect.extend({\n // -----------------------------------------------------------------------\n // toExist — verify an object with the given testId/uuid exists in the scene\n // -----------------------------------------------------------------------\n async toExist(r3f: R3FMatcherReceiver, idOrUuid: string) {\n const meta = await r3f.getObject(idOrUuid);\n const pass = meta !== null;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT exist in the scene, but it does`\n : `Expected object \"${idOrUuid}\" to exist in the scene, but it was not found`,\n name: 'toExist',\n expected: idOrUuid,\n actual: meta,\n };\n },\n\n // -----------------------------------------------------------------------\n // toBeVisible — verify an object is visible (object.visible === true)\n // -----------------------------------------------------------------------\n async toBeVisible(r3f: R3FMatcherReceiver, idOrUuid: string) {\n const meta = await r3f.getObject(idOrUuid);\n if (!meta) {\n return {\n pass: false,\n message: () => `Expected object \"${idOrUuid}\" to be visible, but it was not found in the scene`,\n name: 'toBeVisible',\n };\n }\n\n const pass = meta.visible;\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT be visible, but it is`\n : `Expected object \"${idOrUuid}\" to be visible, but visible=${meta.visible}`,\n name: 'toBeVisible',\n expected: true,\n actual: meta.visible,\n };\n },\n\n // -----------------------------------------------------------------------\n // toBeInFrustum — verify an object's bounding box intersects the camera\n // frustum (i.e. it is potentially on-screen). Uses inspect() for bounds.\n // -----------------------------------------------------------------------\n async toBeInFrustum(r3f: R3FMatcherReceiver, idOrUuid: string) {\n const inspection = await r3f.inspect(idOrUuid);\n if (!inspection) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to be in frustum, but it was not found in the scene`,\n name: 'toBeInFrustum',\n };\n }\n\n // If the bridge can project it to screen, it's in the frustum.\n // We check by verifying bounds exist and are finite.\n const { bounds } = inspection;\n const isFinite = (v: number[]) => v.every(Number.isFinite);\n const pass = isFinite(bounds.min) && isFinite(bounds.max);\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT be in the camera frustum`\n : `Expected object \"${idOrUuid}\" to be in the camera frustum, but its bounds are invalid`,\n name: 'toBeInFrustum',\n expected: 'finite bounds',\n actual: bounds,\n };\n },\n\n // -----------------------------------------------------------------------\n // toHavePosition — verify object position within tolerance\n // -----------------------------------------------------------------------\n async toHavePosition(\n r3f: R3FMatcherReceiver,\n idOrUuid: string,\n expected: [number, number, number],\n tolerance = 0.01,\n ) {\n const meta = await r3f.getObject(idOrUuid);\n if (!meta) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to have position [${expected}], but it was not found`,\n name: 'toHavePosition',\n };\n }\n\n const [ex, ey, ez] = expected;\n const [ax, ay, az] = meta.position;\n const dx = Math.abs(ax - ex);\n const dy = Math.abs(ay - ey);\n const dz = Math.abs(az - ez);\n const pass = dx <= tolerance && dy <= tolerance && dz <= tolerance;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT be at position [${expected}] (±${tolerance})`\n : `Expected object \"${idOrUuid}\" to be at position [${expected}] (±${tolerance}), but it is at [${meta.position}] (delta: [${dx.toFixed(4)}, ${dy.toFixed(4)}, ${dz.toFixed(4)}])`,\n name: 'toHavePosition',\n expected,\n actual: meta.position,\n };\n },\n\n // -----------------------------------------------------------------------\n // toHaveBounds — verify object bounding box (world-space)\n // -----------------------------------------------------------------------\n async toHaveBounds(\n r3f: R3FMatcherReceiver,\n idOrUuid: string,\n expected: { min: [number, number, number]; max: [number, number, number] },\n tolerance = 0.1,\n ) {\n const inspection = await r3f.inspect(idOrUuid);\n if (!inspection) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to have specific bounds, but it was not found`,\n name: 'toHaveBounds',\n };\n }\n\n const { bounds } = inspection;\n const withinTolerance = (a: number[], b: number[]) =>\n a.every((v, i) => Math.abs(v - b[i]) <= tolerance);\n const pass = withinTolerance(bounds.min, expected.min) && withinTolerance(bounds.max, expected.max);\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT have bounds min:${JSON.stringify(expected.min)} max:${JSON.stringify(expected.max)}`\n : `Expected object \"${idOrUuid}\" to have bounds min:${JSON.stringify(expected.min)} max:${JSON.stringify(expected.max)}, but got min:${JSON.stringify(bounds.min)} max:${JSON.stringify(bounds.max)}`,\n name: 'toHaveBounds',\n expected,\n actual: bounds,\n };\n },\n\n // -----------------------------------------------------------------------\n // toHaveInstanceCount — verify InstancedMesh instance count\n // -----------------------------------------------------------------------\n async toHaveInstanceCount(r3f: R3FMatcherReceiver, idOrUuid: string, expectedCount: number) {\n const meta = await r3f.getObject(idOrUuid);\n if (!meta) {\n return {\n pass: false,\n message: () =>\n `Expected object \"${idOrUuid}\" to have instance count ${expectedCount}, but it was not found`,\n name: 'toHaveInstanceCount',\n };\n }\n\n const actual = meta.instanceCount ?? 0;\n const pass = actual === expectedCount;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected object \"${idOrUuid}\" to NOT have instance count ${expectedCount}`\n : `Expected object \"${idOrUuid}\" to have instance count ${expectedCount}, but it has ${actual}`,\n name: 'toHaveInstanceCount',\n expected: expectedCount,\n actual,\n };\n },\n});\n\n// ---------------------------------------------------------------------------\n// Helper type — the R3FFixture object that matchers receive\n// ---------------------------------------------------------------------------\n\ninterface R3FMatcherReceiver {\n getObject(idOrUuid: string): Promise<ObjectMetadata | null>;\n inspect(idOrUuid: string): Promise<ObjectInspection | null>;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-three-dom/playwright",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Playwright E2E testing SDK for React Three Fiber apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",