@react-three-dom/cypress 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,290 @@
1
+ 'use strict';
2
+
3
+ // src/commands.ts
4
+ function getR3F(win) {
5
+ const api = win.__R3F_DOM__;
6
+ if (!api) {
7
+ throw new Error(
8
+ "react-three-dom bridge not found. Is <ThreeDom> mounted in your app?"
9
+ );
10
+ }
11
+ return api;
12
+ }
13
+ function registerCommands() {
14
+ Cypress.Commands.add("r3fClick", (idOrUuid) => {
15
+ cy.window({ log: false }).then((win) => {
16
+ getR3F(win).click(idOrUuid);
17
+ });
18
+ });
19
+ Cypress.Commands.add("r3fDoubleClick", (idOrUuid) => {
20
+ cy.window({ log: false }).then((win) => {
21
+ getR3F(win).doubleClick(idOrUuid);
22
+ });
23
+ });
24
+ Cypress.Commands.add("r3fContextMenu", (idOrUuid) => {
25
+ cy.window({ log: false }).then((win) => {
26
+ getR3F(win).contextMenu(idOrUuid);
27
+ });
28
+ });
29
+ Cypress.Commands.add("r3fHover", (idOrUuid) => {
30
+ cy.window({ log: false }).then((win) => {
31
+ getR3F(win).hover(idOrUuid);
32
+ });
33
+ });
34
+ Cypress.Commands.add(
35
+ "r3fDrag",
36
+ (idOrUuid, delta) => {
37
+ cy.window({ log: false }).then((win) => {
38
+ getR3F(win).drag(idOrUuid, delta);
39
+ });
40
+ }
41
+ );
42
+ Cypress.Commands.add(
43
+ "r3fWheel",
44
+ (idOrUuid, options) => {
45
+ cy.window({ log: false }).then((win) => {
46
+ getR3F(win).wheel(idOrUuid, options);
47
+ });
48
+ }
49
+ );
50
+ Cypress.Commands.add("r3fPointerMiss", () => {
51
+ cy.window({ log: false }).then((win) => {
52
+ getR3F(win).pointerMiss();
53
+ });
54
+ });
55
+ Cypress.Commands.add("r3fSelect", (idOrUuid) => {
56
+ cy.window({ log: false }).then((win) => {
57
+ getR3F(win).select(idOrUuid);
58
+ });
59
+ });
60
+ Cypress.Commands.add("r3fClearSelection", () => {
61
+ cy.window({ log: false }).then((win) => {
62
+ getR3F(win).clearSelection();
63
+ });
64
+ });
65
+ Cypress.Commands.add("r3fGetObject", (idOrUuid) => {
66
+ return cy.window({ log: false }).then((win) => {
67
+ const api = getR3F(win);
68
+ return api.getByTestId(idOrUuid) ?? api.getByUuid(idOrUuid) ?? null;
69
+ });
70
+ });
71
+ Cypress.Commands.add("r3fInspect", (idOrUuid) => {
72
+ return cy.window({ log: false }).then((win) => {
73
+ return getR3F(win).inspect(idOrUuid);
74
+ });
75
+ });
76
+ Cypress.Commands.add("r3fSnapshot", () => {
77
+ return cy.window({ log: false }).then((win) => {
78
+ return getR3F(win).snapshot();
79
+ });
80
+ });
81
+ Cypress.Commands.add("r3fGetCount", () => {
82
+ return cy.window({ log: false }).then((win) => {
83
+ return getR3F(win).getCount();
84
+ });
85
+ });
86
+ }
87
+
88
+ // src/assertions.ts
89
+ function getR3FFromWindow() {
90
+ const win = cy.state("window");
91
+ const api = win?.__R3F_DOM__;
92
+ if (!api) {
93
+ throw new Error("react-three-dom bridge not found. Is <ThreeDom> mounted?");
94
+ }
95
+ return api;
96
+ }
97
+ function resolveObject(api, idOrUuid) {
98
+ return api.getByTestId(idOrUuid) ?? api.getByUuid(idOrUuid) ?? null;
99
+ }
100
+ function registerAssertions() {
101
+ chai.use((_chai) => {
102
+ const { Assertion } = _chai;
103
+ Assertion.addMethod("r3fExist", function(idOrUuid) {
104
+ const api = getR3FFromWindow();
105
+ const meta = resolveObject(api, idOrUuid);
106
+ this.assert(
107
+ meta !== null,
108
+ `expected 3D object "${idOrUuid}" to exist in the scene`,
109
+ `expected 3D object "${idOrUuid}" to NOT exist in the scene`,
110
+ "exists",
111
+ meta === null ? "not found" : "found"
112
+ );
113
+ });
114
+ Assertion.addMethod("r3fVisible", function(idOrUuid) {
115
+ const api = getR3FFromWindow();
116
+ const meta = resolveObject(api, idOrUuid);
117
+ if (!meta) {
118
+ throw new Error(`3D object "${idOrUuid}" not found in the scene`);
119
+ }
120
+ this.assert(
121
+ meta.visible,
122
+ `expected 3D object "${idOrUuid}" to be visible`,
123
+ `expected 3D object "${idOrUuid}" to NOT be visible`,
124
+ true,
125
+ meta.visible
126
+ );
127
+ });
128
+ Assertion.addMethod("r3fInFrustum", function(idOrUuid) {
129
+ const api = getR3FFromWindow();
130
+ const inspection = api.inspect(idOrUuid);
131
+ if (!inspection) {
132
+ throw new Error(`3D object "${idOrUuid}" not found in the scene`);
133
+ }
134
+ const { bounds } = inspection;
135
+ const isFinite = (v) => v.every(Number.isFinite);
136
+ const inFrustum = isFinite(bounds.min) && isFinite(bounds.max);
137
+ this.assert(
138
+ inFrustum,
139
+ `expected 3D object "${idOrUuid}" to be in the camera frustum`,
140
+ `expected 3D object "${idOrUuid}" to NOT be in the camera frustum`,
141
+ "in frustum",
142
+ inFrustum ? "in frustum" : "out of frustum / invalid bounds"
143
+ );
144
+ });
145
+ Assertion.addMethod(
146
+ "r3fPosition",
147
+ function(idOrUuid, expected, tolerance = 0.01) {
148
+ const api = getR3FFromWindow();
149
+ const meta = resolveObject(api, idOrUuid);
150
+ if (!meta) {
151
+ throw new Error(`3D object "${idOrUuid}" not found in the scene`);
152
+ }
153
+ const [ex, ey, ez] = expected;
154
+ const [ax, ay, az] = meta.position;
155
+ const pass = Math.abs(ax - ex) <= tolerance && Math.abs(ay - ey) <= tolerance && Math.abs(az - ez) <= tolerance;
156
+ this.assert(
157
+ pass,
158
+ `expected 3D object "${idOrUuid}" to be at [${expected}] (\xB1${tolerance}), but was at [${meta.position}]`,
159
+ `expected 3D object "${idOrUuid}" to NOT be at [${expected}] (\xB1${tolerance})`,
160
+ expected,
161
+ meta.position
162
+ );
163
+ }
164
+ );
165
+ Assertion.addMethod(
166
+ "r3fInstanceCount",
167
+ function(idOrUuid, expectedCount) {
168
+ const api = getR3FFromWindow();
169
+ const meta = resolveObject(api, idOrUuid);
170
+ if (!meta) {
171
+ throw new Error(`3D object "${idOrUuid}" not found in the scene`);
172
+ }
173
+ const actual = meta.instanceCount ?? 0;
174
+ this.assert(
175
+ actual === expectedCount,
176
+ `expected 3D object "${idOrUuid}" to have instance count ${expectedCount}, but had ${actual}`,
177
+ `expected 3D object "${idOrUuid}" to NOT have instance count ${expectedCount}`,
178
+ expectedCount,
179
+ actual
180
+ );
181
+ }
182
+ );
183
+ Assertion.addMethod(
184
+ "r3fBounds",
185
+ function(idOrUuid, expected, tolerance = 0.1) {
186
+ const api = getR3FFromWindow();
187
+ const inspection = api.inspect(idOrUuid);
188
+ if (!inspection) {
189
+ throw new Error(`3D object "${idOrUuid}" not found in the scene`);
190
+ }
191
+ const { bounds } = inspection;
192
+ const withinTol = (a, b) => a.every((v, i) => Math.abs(v - b[i]) <= tolerance);
193
+ const pass = withinTol(bounds.min, expected.min) && withinTol(bounds.max, expected.max);
194
+ this.assert(
195
+ pass,
196
+ `expected 3D object "${idOrUuid}" bounds to be min:${JSON.stringify(expected.min)} max:${JSON.stringify(expected.max)} (\xB1${tolerance}), but got min:${JSON.stringify(bounds.min)} max:${JSON.stringify(bounds.max)}`,
197
+ `expected 3D object "${idOrUuid}" bounds to NOT match`,
198
+ expected,
199
+ bounds
200
+ );
201
+ }
202
+ );
203
+ });
204
+ }
205
+
206
+ // src/waiters.ts
207
+ function registerWaiters() {
208
+ Cypress.Commands.add(
209
+ "r3fWaitForSceneReady",
210
+ (options = {}) => {
211
+ const {
212
+ stableChecks = 3,
213
+ pollIntervalMs = 100,
214
+ timeout = 1e4
215
+ } = options;
216
+ const startTime = Date.now();
217
+ let lastCount = -1;
218
+ let stableRuns = 0;
219
+ function poll() {
220
+ if (Date.now() - startTime > timeout) {
221
+ throw new Error(
222
+ `r3fWaitForSceneReady timed out after ${timeout}ms. Last count: ${lastCount}, stable runs: ${stableRuns}/${stableChecks}`
223
+ );
224
+ }
225
+ return cy.window({ log: false }).then((win) => {
226
+ const api = win.__R3F_DOM__;
227
+ if (!api) {
228
+ return cy.wait(pollIntervalMs, { log: false }).then(() => poll());
229
+ }
230
+ const count = api.getCount();
231
+ if (count === lastCount && count > 0) {
232
+ stableRuns++;
233
+ if (stableRuns >= stableChecks) {
234
+ return;
235
+ }
236
+ } else {
237
+ stableRuns = 0;
238
+ }
239
+ lastCount = count;
240
+ return cy.wait(pollIntervalMs, { log: false }).then(() => poll());
241
+ });
242
+ }
243
+ return poll();
244
+ }
245
+ );
246
+ Cypress.Commands.add(
247
+ "r3fWaitForIdle",
248
+ (options = {}) => {
249
+ const {
250
+ idleChecks = 10,
251
+ pollIntervalMs = 50,
252
+ timeout = 1e4
253
+ } = options;
254
+ const startTime = Date.now();
255
+ let lastJson = "";
256
+ let stableCount = 0;
257
+ function poll() {
258
+ if (Date.now() - startTime > timeout) {
259
+ throw new Error(`r3fWaitForIdle timed out after ${timeout}ms`);
260
+ }
261
+ return cy.window({ log: false }).then((win) => {
262
+ const api = win.__R3F_DOM__;
263
+ if (!api) {
264
+ return cy.wait(pollIntervalMs, { log: false }).then(() => poll());
265
+ }
266
+ const snap = api.snapshot();
267
+ const json = JSON.stringify(snap.tree);
268
+ if (json === lastJson && json !== "") {
269
+ stableCount++;
270
+ if (stableCount >= idleChecks) {
271
+ return;
272
+ }
273
+ } else {
274
+ stableCount = 0;
275
+ }
276
+ lastJson = json;
277
+ return cy.wait(pollIntervalMs, { log: false }).then(() => poll());
278
+ });
279
+ }
280
+ return poll();
281
+ }
282
+ );
283
+ }
284
+
285
+ // src/index.ts
286
+ registerCommands();
287
+ registerAssertions();
288
+ registerWaiters();
289
+ //# sourceMappingURL=index.cjs.map
290
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands.ts","../src/assertions.ts","../src/waiters.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/// <reference types=\"cypress\" />\nimport type { R3FDOM } from './types';\n\n// ---------------------------------------------------------------------------\n// Helper to get the R3F DOM bridge from the current window\n// ---------------------------------------------------------------------------\n\nfunction getR3F(win: Cypress.AUTWindow): R3FDOM {\n const api = (win as Window & { __R3F_DOM__?: R3FDOM }).__R3F_DOM__;\n if (!api) {\n throw new Error(\n 'react-three-dom bridge not found. Is <ThreeDom> mounted in your app?',\n );\n }\n return api;\n}\n\n// ---------------------------------------------------------------------------\n// Custom commands — all prefixed with `r3f` for clarity\n// ---------------------------------------------------------------------------\n\nexport function registerCommands(): void {\n Cypress.Commands.add('r3fClick', (idOrUuid: string) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).click(idOrUuid);\n });\n });\n\n Cypress.Commands.add('r3fDoubleClick', (idOrUuid: string) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).doubleClick(idOrUuid);\n });\n });\n\n Cypress.Commands.add('r3fContextMenu', (idOrUuid: string) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).contextMenu(idOrUuid);\n });\n });\n\n Cypress.Commands.add('r3fHover', (idOrUuid: string) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).hover(idOrUuid);\n });\n });\n\n Cypress.Commands.add(\n 'r3fDrag',\n (idOrUuid: string, delta: { x: number; y: number; z: number }) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).drag(idOrUuid, delta);\n });\n },\n );\n\n Cypress.Commands.add(\n 'r3fWheel',\n (idOrUuid: string, options?: { deltaY?: number; deltaX?: number }) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).wheel(idOrUuid, options);\n });\n },\n );\n\n Cypress.Commands.add('r3fPointerMiss', () => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).pointerMiss();\n });\n });\n\n Cypress.Commands.add('r3fSelect', (idOrUuid: string) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).select(idOrUuid);\n });\n });\n\n Cypress.Commands.add('r3fClearSelection', () => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).clearSelection();\n });\n });\n\n Cypress.Commands.add('r3fGetObject', (idOrUuid: string) => {\n return cy.window({ log: false }).then((win) => {\n const api = getR3F(win);\n return api.getByTestId(idOrUuid) ?? api.getByUuid(idOrUuid) ?? null;\n });\n });\n\n Cypress.Commands.add('r3fInspect', (idOrUuid: string) => {\n return cy.window({ log: false }).then((win) => {\n return getR3F(win).inspect(idOrUuid);\n });\n });\n\n Cypress.Commands.add('r3fSnapshot', () => {\n return cy.window({ log: false }).then((win) => {\n return getR3F(win).snapshot();\n });\n });\n\n Cypress.Commands.add('r3fGetCount', () => {\n return cy.window({ log: false }).then((win) => {\n return getR3F(win).getCount();\n });\n });\n}\n","/// <reference types=\"cypress\" />\nimport type { R3FDOM, ObjectMetadata } from './types';\n\n// ---------------------------------------------------------------------------\n// Chai-based custom assertions for 3D scene testing\n// ---------------------------------------------------------------------------\n\nfunction getR3FFromWindow(): R3FDOM {\n const win = (cy as unknown as { state: (key: string) => { document: Document } })\n .state('window') as unknown as Window & { __R3F_DOM__?: R3FDOM };\n const api = win?.__R3F_DOM__;\n if (!api) {\n throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n }\n return api;\n}\n\nfunction resolveObject(api: R3FDOM, idOrUuid: string): ObjectMetadata | null {\n return api.getByTestId(idOrUuid) ?? api.getByUuid(idOrUuid) ?? null;\n}\n\nexport function registerAssertions(): void {\n chai.use((_chai) => {\n const { Assertion } = _chai;\n\n // -------------------------------------------------------------------\n // r3fExist — assert that a 3D object with the given id exists\n // -------------------------------------------------------------------\n Assertion.addMethod('r3fExist', function (this: Chai.AssertionStatic, idOrUuid: string) {\n const api = getR3FFromWindow();\n const meta = resolveObject(api, idOrUuid);\n\n (this as unknown as Chai.Assertion).assert(\n meta !== null,\n `expected 3D object \"${idOrUuid}\" to exist in the scene`,\n `expected 3D object \"${idOrUuid}\" to NOT exist in the scene`,\n 'exists',\n meta === null ? 'not found' : 'found',\n );\n });\n\n // -------------------------------------------------------------------\n // r3fVisible — assert that a 3D object is visible\n // -------------------------------------------------------------------\n Assertion.addMethod('r3fVisible', function (this: Chai.AssertionStatic, idOrUuid: string) {\n const api = getR3FFromWindow();\n const meta = resolveObject(api, idOrUuid);\n\n if (!meta) {\n throw new Error(`3D object \"${idOrUuid}\" not found in the scene`);\n }\n\n (this as unknown as Chai.Assertion).assert(\n meta.visible,\n `expected 3D object \"${idOrUuid}\" to be visible`,\n `expected 3D object \"${idOrUuid}\" to NOT be visible`,\n true,\n meta.visible,\n );\n });\n\n // -------------------------------------------------------------------\n // r3fInFrustum — assert that a 3D object is in the camera frustum\n // -------------------------------------------------------------------\n Assertion.addMethod('r3fInFrustum', function (this: Chai.AssertionStatic, idOrUuid: string) {\n const api = getR3FFromWindow();\n const inspection = api.inspect(idOrUuid);\n\n if (!inspection) {\n throw new Error(`3D object \"${idOrUuid}\" not found in the scene`);\n }\n\n const { bounds } = inspection;\n const isFinite = (v: number[]) => v.every(Number.isFinite);\n const inFrustum = isFinite(bounds.min) && isFinite(bounds.max);\n\n (this as unknown as Chai.Assertion).assert(\n inFrustum,\n `expected 3D object \"${idOrUuid}\" to be in the camera frustum`,\n `expected 3D object \"${idOrUuid}\" to NOT be in the camera frustum`,\n 'in frustum',\n inFrustum ? 'in frustum' : 'out of frustum / invalid bounds',\n );\n });\n\n // -------------------------------------------------------------------\n // r3fPosition — assert object position within tolerance\n // -------------------------------------------------------------------\n Assertion.addMethod(\n 'r3fPosition',\n function (\n this: Chai.AssertionStatic,\n idOrUuid: string,\n expected: [number, number, number],\n tolerance = 0.01,\n ) {\n const api = getR3FFromWindow();\n const meta = resolveObject(api, idOrUuid);\n\n if (!meta) {\n throw new Error(`3D object \"${idOrUuid}\" not found in the scene`);\n }\n\n const [ex, ey, ez] = expected;\n const [ax, ay, az] = meta.position;\n const pass =\n Math.abs(ax - ex) <= tolerance &&\n Math.abs(ay - ey) <= tolerance &&\n Math.abs(az - ez) <= tolerance;\n\n (this as unknown as Chai.Assertion).assert(\n pass,\n `expected 3D object \"${idOrUuid}\" to be at [${expected}] (±${tolerance}), but was at [${meta.position}]`,\n `expected 3D object \"${idOrUuid}\" to NOT be at [${expected}] (±${tolerance})`,\n expected,\n meta.position,\n );\n },\n );\n\n // -------------------------------------------------------------------\n // r3fInstanceCount — assert InstancedMesh instance count\n // -------------------------------------------------------------------\n Assertion.addMethod(\n 'r3fInstanceCount',\n function (this: Chai.AssertionStatic, idOrUuid: string, expectedCount: number) {\n const api = getR3FFromWindow();\n const meta = resolveObject(api, idOrUuid);\n\n if (!meta) {\n throw new Error(`3D object \"${idOrUuid}\" not found in the scene`);\n }\n\n const actual = meta.instanceCount ?? 0;\n\n (this as unknown as Chai.Assertion).assert(\n actual === expectedCount,\n `expected 3D object \"${idOrUuid}\" to have instance count ${expectedCount}, but had ${actual}`,\n `expected 3D object \"${idOrUuid}\" to NOT have instance count ${expectedCount}`,\n expectedCount,\n actual,\n );\n },\n );\n\n // -------------------------------------------------------------------\n // r3fBounds — assert world-space bounding box within tolerance\n // -------------------------------------------------------------------\n Assertion.addMethod(\n 'r3fBounds',\n function (\n this: Chai.AssertionStatic,\n idOrUuid: string,\n expected: { min: [number, number, number]; max: [number, number, number] },\n tolerance = 0.1,\n ) {\n const api = getR3FFromWindow();\n const inspection = api.inspect(idOrUuid);\n\n if (!inspection) {\n throw new Error(`3D object \"${idOrUuid}\" not found in the scene`);\n }\n\n const { bounds } = inspection;\n const withinTol = (a: number[], b: number[]) =>\n a.every((v, i) => Math.abs(v - b[i]) <= tolerance);\n const pass = withinTol(bounds.min, expected.min) && withinTol(bounds.max, expected.max);\n\n (this as unknown as Chai.Assertion).assert(\n pass,\n `expected 3D object \"${idOrUuid}\" bounds to be min:${JSON.stringify(expected.min)} max:${JSON.stringify(expected.max)} (±${tolerance}), but got min:${JSON.stringify(bounds.min)} max:${JSON.stringify(bounds.max)}`,\n `expected 3D object \"${idOrUuid}\" bounds to NOT match`,\n expected,\n bounds,\n );\n },\n );\n });\n}\n","/// <reference types=\"cypress\" />\nimport type { R3FDOM } from './types';\n\n// ---------------------------------------------------------------------------\n// r3fWaitForSceneReady — wait until the bridge is ready and object count\n// has stabilised across several consecutive polls.\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\nexport function registerWaiters(): void {\n Cypress.Commands.add(\n 'r3fWaitForSceneReady',\n (options: WaitForSceneReadyOptions = {}) => {\n const {\n stableChecks = 3,\n pollIntervalMs = 100,\n timeout = 10_000,\n } = options;\n\n const startTime = Date.now();\n let lastCount = -1;\n let stableRuns = 0;\n\n function poll(): Cypress.Chainable<void> {\n if (Date.now() - startTime > timeout) {\n throw new Error(\n `r3fWaitForSceneReady timed out after ${timeout}ms. Last count: ${lastCount}, stable runs: ${stableRuns}/${stableChecks}`,\n );\n }\n\n return cy.window({ log: false }).then((win) => {\n const api = (win as Window & { __R3F_DOM__?: R3FDOM }).__R3F_DOM__;\n if (!api) {\n // Bridge not yet available, keep polling\n return cy.wait(pollIntervalMs, { log: false }).then(() => poll());\n }\n\n const count = api.getCount();\n\n if (count === lastCount && count > 0) {\n stableRuns++;\n if (stableRuns >= stableChecks) {\n return; // Done — scene is ready\n }\n } else {\n stableRuns = 0;\n }\n\n lastCount = count;\n return cy.wait(pollIntervalMs, { log: false }).then(() => poll());\n });\n }\n\n return poll();\n },\n );\n\n // -----------------------------------------------------------------------\n // r3fWaitForIdle — wait until no property changes for N polls\n // -----------------------------------------------------------------------\n\n Cypress.Commands.add(\n 'r3fWaitForIdle',\n (options: { idleChecks?: number; pollIntervalMs?: number; timeout?: number } = {}) => {\n const {\n idleChecks = 10,\n pollIntervalMs = 50,\n timeout = 10_000,\n } = options;\n\n const startTime = Date.now();\n let lastJson = '';\n let stableCount = 0;\n\n function poll(): Cypress.Chainable<void> {\n if (Date.now() - startTime > timeout) {\n throw new Error(`r3fWaitForIdle timed out after ${timeout}ms`);\n }\n\n return cy.window({ log: false }).then((win) => {\n const api = (win as Window & { __R3F_DOM__?: R3FDOM }).__R3F_DOM__;\n if (!api) {\n return cy.wait(pollIntervalMs, { log: false }).then(() => poll());\n }\n\n const snap = api.snapshot();\n const json = JSON.stringify(snap.tree);\n\n if (json === lastJson && json !== '') {\n stableCount++;\n if (stableCount >= idleChecks) {\n return; // Done — scene is idle\n }\n } else {\n stableCount = 0;\n }\n\n lastJson = json;\n return cy.wait(pollIntervalMs, { log: false }).then(() => poll());\n });\n }\n\n return poll();\n },\n );\n}\n","// @react-three-dom/cypress\n// Cypress E2E testing SDK for React Three Fiber apps\n//\n// Usage in cypress/support/e2e.ts:\n// import '@react-three-dom/cypress';\n//\n// This auto-registers all custom commands and Chai assertions.\n\n/// <reference types=\"cypress\" />\n\nimport { registerCommands } from './commands';\nimport { registerAssertions } from './assertions';\nimport { registerWaiters } from './waiters';\n\n// Auto-register on import\nregisterCommands();\nregisterAssertions();\nregisterWaiters();\n\n// Re-export types for programmatic use\nexport type {\n ObjectMetadata,\n ObjectInspection,\n SceneSnapshot,\n SnapshotNode,\n R3FDOM,\n} from './types';\n"]}
@@ -0,0 +1,151 @@
1
+ interface ObjectMetadata {
2
+ uuid: string;
3
+ name: string;
4
+ type: string;
5
+ visible: boolean;
6
+ testId?: string;
7
+ geometryType?: string;
8
+ materialType?: string;
9
+ vertexCount?: number;
10
+ triangleCount?: number;
11
+ instanceCount?: number;
12
+ position: [number, number, number];
13
+ rotation: [number, number, number];
14
+ scale: [number, number, number];
15
+ parentUuid: string | null;
16
+ childrenUuids: string[];
17
+ boundsDirty: boolean;
18
+ }
19
+ interface ObjectInspection {
20
+ metadata: ObjectMetadata;
21
+ worldMatrix: number[];
22
+ bounds: {
23
+ min: [number, number, number];
24
+ max: [number, number, number];
25
+ };
26
+ geometry?: {
27
+ type: string;
28
+ attributes: Record<string, {
29
+ itemSize: number;
30
+ count: number;
31
+ }>;
32
+ index?: {
33
+ count: number;
34
+ };
35
+ boundingSphere?: {
36
+ center: [number, number, number];
37
+ radius: number;
38
+ };
39
+ };
40
+ material?: {
41
+ type: string;
42
+ color?: string;
43
+ map?: string;
44
+ uniforms?: Record<string, unknown>;
45
+ transparent?: boolean;
46
+ opacity?: number;
47
+ side?: number;
48
+ };
49
+ userData: Record<string, unknown>;
50
+ }
51
+ interface SnapshotNode {
52
+ uuid: string;
53
+ name: string;
54
+ type: string;
55
+ testId?: string;
56
+ visible: boolean;
57
+ position: [number, number, number];
58
+ rotation: [number, number, number];
59
+ scale: [number, number, number];
60
+ children: SnapshotNode[];
61
+ }
62
+ interface SceneSnapshot {
63
+ timestamp: number;
64
+ objectCount: number;
65
+ tree: SnapshotNode;
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // TypeScript declarations for @react-three-dom/cypress
70
+ // Extends Cypress.Chainable and Cypress.Assertion for full type safety.
71
+ //
72
+ // Usage: add to your cypress/support/e2e.ts:
73
+ // import '@react-three-dom/cypress';
74
+ // ---------------------------------------------------------------------------
75
+
76
+
77
+
78
+ declare global {
79
+ namespace Cypress {
80
+ interface Chainable {
81
+ // ---- Interactions ----
82
+ /** Click a 3D object by testId or uuid. */
83
+ r3fClick(idOrUuid: string): Chainable<void>;
84
+ /** Double-click a 3D object by testId or uuid. */
85
+ r3fDoubleClick(idOrUuid: string): Chainable<void>;
86
+ /** Right-click / context-menu a 3D object by testId or uuid. */
87
+ r3fContextMenu(idOrUuid: string): Chainable<void>;
88
+ /** Hover over a 3D object by testId or uuid. */
89
+ r3fHover(idOrUuid: string): Chainable<void>;
90
+ /** Drag a 3D object with a world-space delta vector. */
91
+ r3fDrag(idOrUuid: string, delta: { x: number; y: number; z: number }): Chainable<void>;
92
+ /** Dispatch a wheel/scroll event on a 3D object. */
93
+ r3fWheel(idOrUuid: string, options?: { deltaY?: number; deltaX?: number }): Chainable<void>;
94
+ /** Click empty space to trigger onPointerMissed handlers. */
95
+ r3fPointerMiss(): Chainable<void>;
96
+
97
+ // ---- Selection ----
98
+ /** Select a 3D object (highlights in scene). */
99
+ r3fSelect(idOrUuid: string): Chainable<void>;
100
+ /** Clear the current selection. */
101
+ r3fClearSelection(): Chainable<void>;
102
+
103
+ // ---- Queries ----
104
+ /** Get object metadata by testId or uuid. */
105
+ r3fGetObject(idOrUuid: string): Chainable<ObjectMetadata | null>;
106
+ /** Get heavy inspection data (Tier 2) by testId or uuid. */
107
+ r3fInspect(idOrUuid: string): Chainable<ObjectInspection | null>;
108
+ /** Take a full scene snapshot. */
109
+ r3fSnapshot(): Chainable<SceneSnapshot>;
110
+ /** Get the total number of tracked objects. */
111
+ r3fGetCount(): Chainable<number>;
112
+
113
+ // ---- Waiters ----
114
+ /** Wait until the scene is ready (bridge available, object count stable). */
115
+ r3fWaitForSceneReady(options?: {
116
+ stableChecks?: number;
117
+ pollIntervalMs?: number;
118
+ timeout?: number;
119
+ }): Chainable<void>;
120
+ /** Wait until no property changes for N consecutive polls. */
121
+ r3fWaitForIdle(options?: {
122
+ idleChecks?: number;
123
+ pollIntervalMs?: number;
124
+ timeout?: number;
125
+ }): Chainable<void>;
126
+ }
127
+
128
+ interface Assertion {
129
+ /** Assert that a 3D object with the given testId/uuid exists. */
130
+ r3fExist(idOrUuid: string): Assertion;
131
+ /** Assert that a 3D object is visible. */
132
+ r3fVisible(idOrUuid: string): Assertion;
133
+ /** Assert that a 3D object is in the camera frustum. */
134
+ r3fInFrustum(idOrUuid: string): Assertion;
135
+ /** Assert object position within tolerance. */
136
+ r3fPosition(
137
+ idOrUuid: string,
138
+ expected: [number, number, number],
139
+ tolerance?: number,
140
+ ): Assertion;
141
+ /** Assert InstancedMesh instance count. */
142
+ r3fInstanceCount(idOrUuid: string, expectedCount: number): Assertion;
143
+ /** Assert world-space bounding box within tolerance. */
144
+ r3fBounds(
145
+ idOrUuid: string,
146
+ expected: { min: [number, number, number]; max: [number, number, number] },
147
+ tolerance?: number,
148
+ ): Assertion;
149
+ }
150
+ }
151
+ }
@@ -0,0 +1,151 @@
1
+ interface ObjectMetadata {
2
+ uuid: string;
3
+ name: string;
4
+ type: string;
5
+ visible: boolean;
6
+ testId?: string;
7
+ geometryType?: string;
8
+ materialType?: string;
9
+ vertexCount?: number;
10
+ triangleCount?: number;
11
+ instanceCount?: number;
12
+ position: [number, number, number];
13
+ rotation: [number, number, number];
14
+ scale: [number, number, number];
15
+ parentUuid: string | null;
16
+ childrenUuids: string[];
17
+ boundsDirty: boolean;
18
+ }
19
+ interface ObjectInspection {
20
+ metadata: ObjectMetadata;
21
+ worldMatrix: number[];
22
+ bounds: {
23
+ min: [number, number, number];
24
+ max: [number, number, number];
25
+ };
26
+ geometry?: {
27
+ type: string;
28
+ attributes: Record<string, {
29
+ itemSize: number;
30
+ count: number;
31
+ }>;
32
+ index?: {
33
+ count: number;
34
+ };
35
+ boundingSphere?: {
36
+ center: [number, number, number];
37
+ radius: number;
38
+ };
39
+ };
40
+ material?: {
41
+ type: string;
42
+ color?: string;
43
+ map?: string;
44
+ uniforms?: Record<string, unknown>;
45
+ transparent?: boolean;
46
+ opacity?: number;
47
+ side?: number;
48
+ };
49
+ userData: Record<string, unknown>;
50
+ }
51
+ interface SnapshotNode {
52
+ uuid: string;
53
+ name: string;
54
+ type: string;
55
+ testId?: string;
56
+ visible: boolean;
57
+ position: [number, number, number];
58
+ rotation: [number, number, number];
59
+ scale: [number, number, number];
60
+ children: SnapshotNode[];
61
+ }
62
+ interface SceneSnapshot {
63
+ timestamp: number;
64
+ objectCount: number;
65
+ tree: SnapshotNode;
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // TypeScript declarations for @react-three-dom/cypress
70
+ // Extends Cypress.Chainable and Cypress.Assertion for full type safety.
71
+ //
72
+ // Usage: add to your cypress/support/e2e.ts:
73
+ // import '@react-three-dom/cypress';
74
+ // ---------------------------------------------------------------------------
75
+
76
+
77
+
78
+ declare global {
79
+ namespace Cypress {
80
+ interface Chainable {
81
+ // ---- Interactions ----
82
+ /** Click a 3D object by testId or uuid. */
83
+ r3fClick(idOrUuid: string): Chainable<void>;
84
+ /** Double-click a 3D object by testId or uuid. */
85
+ r3fDoubleClick(idOrUuid: string): Chainable<void>;
86
+ /** Right-click / context-menu a 3D object by testId or uuid. */
87
+ r3fContextMenu(idOrUuid: string): Chainable<void>;
88
+ /** Hover over a 3D object by testId or uuid. */
89
+ r3fHover(idOrUuid: string): Chainable<void>;
90
+ /** Drag a 3D object with a world-space delta vector. */
91
+ r3fDrag(idOrUuid: string, delta: { x: number; y: number; z: number }): Chainable<void>;
92
+ /** Dispatch a wheel/scroll event on a 3D object. */
93
+ r3fWheel(idOrUuid: string, options?: { deltaY?: number; deltaX?: number }): Chainable<void>;
94
+ /** Click empty space to trigger onPointerMissed handlers. */
95
+ r3fPointerMiss(): Chainable<void>;
96
+
97
+ // ---- Selection ----
98
+ /** Select a 3D object (highlights in scene). */
99
+ r3fSelect(idOrUuid: string): Chainable<void>;
100
+ /** Clear the current selection. */
101
+ r3fClearSelection(): Chainable<void>;
102
+
103
+ // ---- Queries ----
104
+ /** Get object metadata by testId or uuid. */
105
+ r3fGetObject(idOrUuid: string): Chainable<ObjectMetadata | null>;
106
+ /** Get heavy inspection data (Tier 2) by testId or uuid. */
107
+ r3fInspect(idOrUuid: string): Chainable<ObjectInspection | null>;
108
+ /** Take a full scene snapshot. */
109
+ r3fSnapshot(): Chainable<SceneSnapshot>;
110
+ /** Get the total number of tracked objects. */
111
+ r3fGetCount(): Chainable<number>;
112
+
113
+ // ---- Waiters ----
114
+ /** Wait until the scene is ready (bridge available, object count stable). */
115
+ r3fWaitForSceneReady(options?: {
116
+ stableChecks?: number;
117
+ pollIntervalMs?: number;
118
+ timeout?: number;
119
+ }): Chainable<void>;
120
+ /** Wait until no property changes for N consecutive polls. */
121
+ r3fWaitForIdle(options?: {
122
+ idleChecks?: number;
123
+ pollIntervalMs?: number;
124
+ timeout?: number;
125
+ }): Chainable<void>;
126
+ }
127
+
128
+ interface Assertion {
129
+ /** Assert that a 3D object with the given testId/uuid exists. */
130
+ r3fExist(idOrUuid: string): Assertion;
131
+ /** Assert that a 3D object is visible. */
132
+ r3fVisible(idOrUuid: string): Assertion;
133
+ /** Assert that a 3D object is in the camera frustum. */
134
+ r3fInFrustum(idOrUuid: string): Assertion;
135
+ /** Assert object position within tolerance. */
136
+ r3fPosition(
137
+ idOrUuid: string,
138
+ expected: [number, number, number],
139
+ tolerance?: number,
140
+ ): Assertion;
141
+ /** Assert InstancedMesh instance count. */
142
+ r3fInstanceCount(idOrUuid: string, expectedCount: number): Assertion;
143
+ /** Assert world-space bounding box within tolerance. */
144
+ r3fBounds(
145
+ idOrUuid: string,
146
+ expected: { min: [number, number, number]; max: [number, number, number] },
147
+ tolerance?: number,
148
+ ): Assertion;
149
+ }
150
+ }
151
+ }
package/dist/index.js ADDED
@@ -0,0 +1,288 @@
1
+ // src/commands.ts
2
+ function getR3F(win) {
3
+ const api = win.__R3F_DOM__;
4
+ if (!api) {
5
+ throw new Error(
6
+ "react-three-dom bridge not found. Is <ThreeDom> mounted in your app?"
7
+ );
8
+ }
9
+ return api;
10
+ }
11
+ function registerCommands() {
12
+ Cypress.Commands.add("r3fClick", (idOrUuid) => {
13
+ cy.window({ log: false }).then((win) => {
14
+ getR3F(win).click(idOrUuid);
15
+ });
16
+ });
17
+ Cypress.Commands.add("r3fDoubleClick", (idOrUuid) => {
18
+ cy.window({ log: false }).then((win) => {
19
+ getR3F(win).doubleClick(idOrUuid);
20
+ });
21
+ });
22
+ Cypress.Commands.add("r3fContextMenu", (idOrUuid) => {
23
+ cy.window({ log: false }).then((win) => {
24
+ getR3F(win).contextMenu(idOrUuid);
25
+ });
26
+ });
27
+ Cypress.Commands.add("r3fHover", (idOrUuid) => {
28
+ cy.window({ log: false }).then((win) => {
29
+ getR3F(win).hover(idOrUuid);
30
+ });
31
+ });
32
+ Cypress.Commands.add(
33
+ "r3fDrag",
34
+ (idOrUuid, delta) => {
35
+ cy.window({ log: false }).then((win) => {
36
+ getR3F(win).drag(idOrUuid, delta);
37
+ });
38
+ }
39
+ );
40
+ Cypress.Commands.add(
41
+ "r3fWheel",
42
+ (idOrUuid, options) => {
43
+ cy.window({ log: false }).then((win) => {
44
+ getR3F(win).wheel(idOrUuid, options);
45
+ });
46
+ }
47
+ );
48
+ Cypress.Commands.add("r3fPointerMiss", () => {
49
+ cy.window({ log: false }).then((win) => {
50
+ getR3F(win).pointerMiss();
51
+ });
52
+ });
53
+ Cypress.Commands.add("r3fSelect", (idOrUuid) => {
54
+ cy.window({ log: false }).then((win) => {
55
+ getR3F(win).select(idOrUuid);
56
+ });
57
+ });
58
+ Cypress.Commands.add("r3fClearSelection", () => {
59
+ cy.window({ log: false }).then((win) => {
60
+ getR3F(win).clearSelection();
61
+ });
62
+ });
63
+ Cypress.Commands.add("r3fGetObject", (idOrUuid) => {
64
+ return cy.window({ log: false }).then((win) => {
65
+ const api = getR3F(win);
66
+ return api.getByTestId(idOrUuid) ?? api.getByUuid(idOrUuid) ?? null;
67
+ });
68
+ });
69
+ Cypress.Commands.add("r3fInspect", (idOrUuid) => {
70
+ return cy.window({ log: false }).then((win) => {
71
+ return getR3F(win).inspect(idOrUuid);
72
+ });
73
+ });
74
+ Cypress.Commands.add("r3fSnapshot", () => {
75
+ return cy.window({ log: false }).then((win) => {
76
+ return getR3F(win).snapshot();
77
+ });
78
+ });
79
+ Cypress.Commands.add("r3fGetCount", () => {
80
+ return cy.window({ log: false }).then((win) => {
81
+ return getR3F(win).getCount();
82
+ });
83
+ });
84
+ }
85
+
86
+ // src/assertions.ts
87
+ function getR3FFromWindow() {
88
+ const win = cy.state("window");
89
+ const api = win?.__R3F_DOM__;
90
+ if (!api) {
91
+ throw new Error("react-three-dom bridge not found. Is <ThreeDom> mounted?");
92
+ }
93
+ return api;
94
+ }
95
+ function resolveObject(api, idOrUuid) {
96
+ return api.getByTestId(idOrUuid) ?? api.getByUuid(idOrUuid) ?? null;
97
+ }
98
+ function registerAssertions() {
99
+ chai.use((_chai) => {
100
+ const { Assertion } = _chai;
101
+ Assertion.addMethod("r3fExist", function(idOrUuid) {
102
+ const api = getR3FFromWindow();
103
+ const meta = resolveObject(api, idOrUuid);
104
+ this.assert(
105
+ meta !== null,
106
+ `expected 3D object "${idOrUuid}" to exist in the scene`,
107
+ `expected 3D object "${idOrUuid}" to NOT exist in the scene`,
108
+ "exists",
109
+ meta === null ? "not found" : "found"
110
+ );
111
+ });
112
+ Assertion.addMethod("r3fVisible", function(idOrUuid) {
113
+ const api = getR3FFromWindow();
114
+ const meta = resolveObject(api, idOrUuid);
115
+ if (!meta) {
116
+ throw new Error(`3D object "${idOrUuid}" not found in the scene`);
117
+ }
118
+ this.assert(
119
+ meta.visible,
120
+ `expected 3D object "${idOrUuid}" to be visible`,
121
+ `expected 3D object "${idOrUuid}" to NOT be visible`,
122
+ true,
123
+ meta.visible
124
+ );
125
+ });
126
+ Assertion.addMethod("r3fInFrustum", function(idOrUuid) {
127
+ const api = getR3FFromWindow();
128
+ const inspection = api.inspect(idOrUuid);
129
+ if (!inspection) {
130
+ throw new Error(`3D object "${idOrUuid}" not found in the scene`);
131
+ }
132
+ const { bounds } = inspection;
133
+ const isFinite = (v) => v.every(Number.isFinite);
134
+ const inFrustum = isFinite(bounds.min) && isFinite(bounds.max);
135
+ this.assert(
136
+ inFrustum,
137
+ `expected 3D object "${idOrUuid}" to be in the camera frustum`,
138
+ `expected 3D object "${idOrUuid}" to NOT be in the camera frustum`,
139
+ "in frustum",
140
+ inFrustum ? "in frustum" : "out of frustum / invalid bounds"
141
+ );
142
+ });
143
+ Assertion.addMethod(
144
+ "r3fPosition",
145
+ function(idOrUuid, expected, tolerance = 0.01) {
146
+ const api = getR3FFromWindow();
147
+ const meta = resolveObject(api, idOrUuid);
148
+ if (!meta) {
149
+ throw new Error(`3D object "${idOrUuid}" not found in the scene`);
150
+ }
151
+ const [ex, ey, ez] = expected;
152
+ const [ax, ay, az] = meta.position;
153
+ const pass = Math.abs(ax - ex) <= tolerance && Math.abs(ay - ey) <= tolerance && Math.abs(az - ez) <= tolerance;
154
+ this.assert(
155
+ pass,
156
+ `expected 3D object "${idOrUuid}" to be at [${expected}] (\xB1${tolerance}), but was at [${meta.position}]`,
157
+ `expected 3D object "${idOrUuid}" to NOT be at [${expected}] (\xB1${tolerance})`,
158
+ expected,
159
+ meta.position
160
+ );
161
+ }
162
+ );
163
+ Assertion.addMethod(
164
+ "r3fInstanceCount",
165
+ function(idOrUuid, expectedCount) {
166
+ const api = getR3FFromWindow();
167
+ const meta = resolveObject(api, idOrUuid);
168
+ if (!meta) {
169
+ throw new Error(`3D object "${idOrUuid}" not found in the scene`);
170
+ }
171
+ const actual = meta.instanceCount ?? 0;
172
+ this.assert(
173
+ actual === expectedCount,
174
+ `expected 3D object "${idOrUuid}" to have instance count ${expectedCount}, but had ${actual}`,
175
+ `expected 3D object "${idOrUuid}" to NOT have instance count ${expectedCount}`,
176
+ expectedCount,
177
+ actual
178
+ );
179
+ }
180
+ );
181
+ Assertion.addMethod(
182
+ "r3fBounds",
183
+ function(idOrUuid, expected, tolerance = 0.1) {
184
+ const api = getR3FFromWindow();
185
+ const inspection = api.inspect(idOrUuid);
186
+ if (!inspection) {
187
+ throw new Error(`3D object "${idOrUuid}" not found in the scene`);
188
+ }
189
+ const { bounds } = inspection;
190
+ const withinTol = (a, b) => a.every((v, i) => Math.abs(v - b[i]) <= tolerance);
191
+ const pass = withinTol(bounds.min, expected.min) && withinTol(bounds.max, expected.max);
192
+ this.assert(
193
+ pass,
194
+ `expected 3D object "${idOrUuid}" bounds to be min:${JSON.stringify(expected.min)} max:${JSON.stringify(expected.max)} (\xB1${tolerance}), but got min:${JSON.stringify(bounds.min)} max:${JSON.stringify(bounds.max)}`,
195
+ `expected 3D object "${idOrUuid}" bounds to NOT match`,
196
+ expected,
197
+ bounds
198
+ );
199
+ }
200
+ );
201
+ });
202
+ }
203
+
204
+ // src/waiters.ts
205
+ function registerWaiters() {
206
+ Cypress.Commands.add(
207
+ "r3fWaitForSceneReady",
208
+ (options = {}) => {
209
+ const {
210
+ stableChecks = 3,
211
+ pollIntervalMs = 100,
212
+ timeout = 1e4
213
+ } = options;
214
+ const startTime = Date.now();
215
+ let lastCount = -1;
216
+ let stableRuns = 0;
217
+ function poll() {
218
+ if (Date.now() - startTime > timeout) {
219
+ throw new Error(
220
+ `r3fWaitForSceneReady timed out after ${timeout}ms. Last count: ${lastCount}, stable runs: ${stableRuns}/${stableChecks}`
221
+ );
222
+ }
223
+ return cy.window({ log: false }).then((win) => {
224
+ const api = win.__R3F_DOM__;
225
+ if (!api) {
226
+ return cy.wait(pollIntervalMs, { log: false }).then(() => poll());
227
+ }
228
+ const count = api.getCount();
229
+ if (count === lastCount && count > 0) {
230
+ stableRuns++;
231
+ if (stableRuns >= stableChecks) {
232
+ return;
233
+ }
234
+ } else {
235
+ stableRuns = 0;
236
+ }
237
+ lastCount = count;
238
+ return cy.wait(pollIntervalMs, { log: false }).then(() => poll());
239
+ });
240
+ }
241
+ return poll();
242
+ }
243
+ );
244
+ Cypress.Commands.add(
245
+ "r3fWaitForIdle",
246
+ (options = {}) => {
247
+ const {
248
+ idleChecks = 10,
249
+ pollIntervalMs = 50,
250
+ timeout = 1e4
251
+ } = options;
252
+ const startTime = Date.now();
253
+ let lastJson = "";
254
+ let stableCount = 0;
255
+ function poll() {
256
+ if (Date.now() - startTime > timeout) {
257
+ throw new Error(`r3fWaitForIdle timed out after ${timeout}ms`);
258
+ }
259
+ return cy.window({ log: false }).then((win) => {
260
+ const api = win.__R3F_DOM__;
261
+ if (!api) {
262
+ return cy.wait(pollIntervalMs, { log: false }).then(() => poll());
263
+ }
264
+ const snap = api.snapshot();
265
+ const json = JSON.stringify(snap.tree);
266
+ if (json === lastJson && json !== "") {
267
+ stableCount++;
268
+ if (stableCount >= idleChecks) {
269
+ return;
270
+ }
271
+ } else {
272
+ stableCount = 0;
273
+ }
274
+ lastJson = json;
275
+ return cy.wait(pollIntervalMs, { log: false }).then(() => poll());
276
+ });
277
+ }
278
+ return poll();
279
+ }
280
+ );
281
+ }
282
+
283
+ // src/index.ts
284
+ registerCommands();
285
+ registerAssertions();
286
+ registerWaiters();
287
+ //# sourceMappingURL=index.js.map
288
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands.ts","../src/assertions.ts","../src/waiters.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/// <reference types=\"cypress\" />\nimport type { R3FDOM } from './types';\n\n// ---------------------------------------------------------------------------\n// Helper to get the R3F DOM bridge from the current window\n// ---------------------------------------------------------------------------\n\nfunction getR3F(win: Cypress.AUTWindow): R3FDOM {\n const api = (win as Window & { __R3F_DOM__?: R3FDOM }).__R3F_DOM__;\n if (!api) {\n throw new Error(\n 'react-three-dom bridge not found. Is <ThreeDom> mounted in your app?',\n );\n }\n return api;\n}\n\n// ---------------------------------------------------------------------------\n// Custom commands — all prefixed with `r3f` for clarity\n// ---------------------------------------------------------------------------\n\nexport function registerCommands(): void {\n Cypress.Commands.add('r3fClick', (idOrUuid: string) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).click(idOrUuid);\n });\n });\n\n Cypress.Commands.add('r3fDoubleClick', (idOrUuid: string) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).doubleClick(idOrUuid);\n });\n });\n\n Cypress.Commands.add('r3fContextMenu', (idOrUuid: string) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).contextMenu(idOrUuid);\n });\n });\n\n Cypress.Commands.add('r3fHover', (idOrUuid: string) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).hover(idOrUuid);\n });\n });\n\n Cypress.Commands.add(\n 'r3fDrag',\n (idOrUuid: string, delta: { x: number; y: number; z: number }) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).drag(idOrUuid, delta);\n });\n },\n );\n\n Cypress.Commands.add(\n 'r3fWheel',\n (idOrUuid: string, options?: { deltaY?: number; deltaX?: number }) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).wheel(idOrUuid, options);\n });\n },\n );\n\n Cypress.Commands.add('r3fPointerMiss', () => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).pointerMiss();\n });\n });\n\n Cypress.Commands.add('r3fSelect', (idOrUuid: string) => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).select(idOrUuid);\n });\n });\n\n Cypress.Commands.add('r3fClearSelection', () => {\n cy.window({ log: false }).then((win) => {\n getR3F(win).clearSelection();\n });\n });\n\n Cypress.Commands.add('r3fGetObject', (idOrUuid: string) => {\n return cy.window({ log: false }).then((win) => {\n const api = getR3F(win);\n return api.getByTestId(idOrUuid) ?? api.getByUuid(idOrUuid) ?? null;\n });\n });\n\n Cypress.Commands.add('r3fInspect', (idOrUuid: string) => {\n return cy.window({ log: false }).then((win) => {\n return getR3F(win).inspect(idOrUuid);\n });\n });\n\n Cypress.Commands.add('r3fSnapshot', () => {\n return cy.window({ log: false }).then((win) => {\n return getR3F(win).snapshot();\n });\n });\n\n Cypress.Commands.add('r3fGetCount', () => {\n return cy.window({ log: false }).then((win) => {\n return getR3F(win).getCount();\n });\n });\n}\n","/// <reference types=\"cypress\" />\nimport type { R3FDOM, ObjectMetadata } from './types';\n\n// ---------------------------------------------------------------------------\n// Chai-based custom assertions for 3D scene testing\n// ---------------------------------------------------------------------------\n\nfunction getR3FFromWindow(): R3FDOM {\n const win = (cy as unknown as { state: (key: string) => { document: Document } })\n .state('window') as unknown as Window & { __R3F_DOM__?: R3FDOM };\n const api = win?.__R3F_DOM__;\n if (!api) {\n throw new Error('react-three-dom bridge not found. Is <ThreeDom> mounted?');\n }\n return api;\n}\n\nfunction resolveObject(api: R3FDOM, idOrUuid: string): ObjectMetadata | null {\n return api.getByTestId(idOrUuid) ?? api.getByUuid(idOrUuid) ?? null;\n}\n\nexport function registerAssertions(): void {\n chai.use((_chai) => {\n const { Assertion } = _chai;\n\n // -------------------------------------------------------------------\n // r3fExist — assert that a 3D object with the given id exists\n // -------------------------------------------------------------------\n Assertion.addMethod('r3fExist', function (this: Chai.AssertionStatic, idOrUuid: string) {\n const api = getR3FFromWindow();\n const meta = resolveObject(api, idOrUuid);\n\n (this as unknown as Chai.Assertion).assert(\n meta !== null,\n `expected 3D object \"${idOrUuid}\" to exist in the scene`,\n `expected 3D object \"${idOrUuid}\" to NOT exist in the scene`,\n 'exists',\n meta === null ? 'not found' : 'found',\n );\n });\n\n // -------------------------------------------------------------------\n // r3fVisible — assert that a 3D object is visible\n // -------------------------------------------------------------------\n Assertion.addMethod('r3fVisible', function (this: Chai.AssertionStatic, idOrUuid: string) {\n const api = getR3FFromWindow();\n const meta = resolveObject(api, idOrUuid);\n\n if (!meta) {\n throw new Error(`3D object \"${idOrUuid}\" not found in the scene`);\n }\n\n (this as unknown as Chai.Assertion).assert(\n meta.visible,\n `expected 3D object \"${idOrUuid}\" to be visible`,\n `expected 3D object \"${idOrUuid}\" to NOT be visible`,\n true,\n meta.visible,\n );\n });\n\n // -------------------------------------------------------------------\n // r3fInFrustum — assert that a 3D object is in the camera frustum\n // -------------------------------------------------------------------\n Assertion.addMethod('r3fInFrustum', function (this: Chai.AssertionStatic, idOrUuid: string) {\n const api = getR3FFromWindow();\n const inspection = api.inspect(idOrUuid);\n\n if (!inspection) {\n throw new Error(`3D object \"${idOrUuid}\" not found in the scene`);\n }\n\n const { bounds } = inspection;\n const isFinite = (v: number[]) => v.every(Number.isFinite);\n const inFrustum = isFinite(bounds.min) && isFinite(bounds.max);\n\n (this as unknown as Chai.Assertion).assert(\n inFrustum,\n `expected 3D object \"${idOrUuid}\" to be in the camera frustum`,\n `expected 3D object \"${idOrUuid}\" to NOT be in the camera frustum`,\n 'in frustum',\n inFrustum ? 'in frustum' : 'out of frustum / invalid bounds',\n );\n });\n\n // -------------------------------------------------------------------\n // r3fPosition — assert object position within tolerance\n // -------------------------------------------------------------------\n Assertion.addMethod(\n 'r3fPosition',\n function (\n this: Chai.AssertionStatic,\n idOrUuid: string,\n expected: [number, number, number],\n tolerance = 0.01,\n ) {\n const api = getR3FFromWindow();\n const meta = resolveObject(api, idOrUuid);\n\n if (!meta) {\n throw new Error(`3D object \"${idOrUuid}\" not found in the scene`);\n }\n\n const [ex, ey, ez] = expected;\n const [ax, ay, az] = meta.position;\n const pass =\n Math.abs(ax - ex) <= tolerance &&\n Math.abs(ay - ey) <= tolerance &&\n Math.abs(az - ez) <= tolerance;\n\n (this as unknown as Chai.Assertion).assert(\n pass,\n `expected 3D object \"${idOrUuid}\" to be at [${expected}] (±${tolerance}), but was at [${meta.position}]`,\n `expected 3D object \"${idOrUuid}\" to NOT be at [${expected}] (±${tolerance})`,\n expected,\n meta.position,\n );\n },\n );\n\n // -------------------------------------------------------------------\n // r3fInstanceCount — assert InstancedMesh instance count\n // -------------------------------------------------------------------\n Assertion.addMethod(\n 'r3fInstanceCount',\n function (this: Chai.AssertionStatic, idOrUuid: string, expectedCount: number) {\n const api = getR3FFromWindow();\n const meta = resolveObject(api, idOrUuid);\n\n if (!meta) {\n throw new Error(`3D object \"${idOrUuid}\" not found in the scene`);\n }\n\n const actual = meta.instanceCount ?? 0;\n\n (this as unknown as Chai.Assertion).assert(\n actual === expectedCount,\n `expected 3D object \"${idOrUuid}\" to have instance count ${expectedCount}, but had ${actual}`,\n `expected 3D object \"${idOrUuid}\" to NOT have instance count ${expectedCount}`,\n expectedCount,\n actual,\n );\n },\n );\n\n // -------------------------------------------------------------------\n // r3fBounds — assert world-space bounding box within tolerance\n // -------------------------------------------------------------------\n Assertion.addMethod(\n 'r3fBounds',\n function (\n this: Chai.AssertionStatic,\n idOrUuid: string,\n expected: { min: [number, number, number]; max: [number, number, number] },\n tolerance = 0.1,\n ) {\n const api = getR3FFromWindow();\n const inspection = api.inspect(idOrUuid);\n\n if (!inspection) {\n throw new Error(`3D object \"${idOrUuid}\" not found in the scene`);\n }\n\n const { bounds } = inspection;\n const withinTol = (a: number[], b: number[]) =>\n a.every((v, i) => Math.abs(v - b[i]) <= tolerance);\n const pass = withinTol(bounds.min, expected.min) && withinTol(bounds.max, expected.max);\n\n (this as unknown as Chai.Assertion).assert(\n pass,\n `expected 3D object \"${idOrUuid}\" bounds to be min:${JSON.stringify(expected.min)} max:${JSON.stringify(expected.max)} (±${tolerance}), but got min:${JSON.stringify(bounds.min)} max:${JSON.stringify(bounds.max)}`,\n `expected 3D object \"${idOrUuid}\" bounds to NOT match`,\n expected,\n bounds,\n );\n },\n );\n });\n}\n","/// <reference types=\"cypress\" />\nimport type { R3FDOM } from './types';\n\n// ---------------------------------------------------------------------------\n// r3fWaitForSceneReady — wait until the bridge is ready and object count\n// has stabilised across several consecutive polls.\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\nexport function registerWaiters(): void {\n Cypress.Commands.add(\n 'r3fWaitForSceneReady',\n (options: WaitForSceneReadyOptions = {}) => {\n const {\n stableChecks = 3,\n pollIntervalMs = 100,\n timeout = 10_000,\n } = options;\n\n const startTime = Date.now();\n let lastCount = -1;\n let stableRuns = 0;\n\n function poll(): Cypress.Chainable<void> {\n if (Date.now() - startTime > timeout) {\n throw new Error(\n `r3fWaitForSceneReady timed out after ${timeout}ms. Last count: ${lastCount}, stable runs: ${stableRuns}/${stableChecks}`,\n );\n }\n\n return cy.window({ log: false }).then((win) => {\n const api = (win as Window & { __R3F_DOM__?: R3FDOM }).__R3F_DOM__;\n if (!api) {\n // Bridge not yet available, keep polling\n return cy.wait(pollIntervalMs, { log: false }).then(() => poll());\n }\n\n const count = api.getCount();\n\n if (count === lastCount && count > 0) {\n stableRuns++;\n if (stableRuns >= stableChecks) {\n return; // Done — scene is ready\n }\n } else {\n stableRuns = 0;\n }\n\n lastCount = count;\n return cy.wait(pollIntervalMs, { log: false }).then(() => poll());\n });\n }\n\n return poll();\n },\n );\n\n // -----------------------------------------------------------------------\n // r3fWaitForIdle — wait until no property changes for N polls\n // -----------------------------------------------------------------------\n\n Cypress.Commands.add(\n 'r3fWaitForIdle',\n (options: { idleChecks?: number; pollIntervalMs?: number; timeout?: number } = {}) => {\n const {\n idleChecks = 10,\n pollIntervalMs = 50,\n timeout = 10_000,\n } = options;\n\n const startTime = Date.now();\n let lastJson = '';\n let stableCount = 0;\n\n function poll(): Cypress.Chainable<void> {\n if (Date.now() - startTime > timeout) {\n throw new Error(`r3fWaitForIdle timed out after ${timeout}ms`);\n }\n\n return cy.window({ log: false }).then((win) => {\n const api = (win as Window & { __R3F_DOM__?: R3FDOM }).__R3F_DOM__;\n if (!api) {\n return cy.wait(pollIntervalMs, { log: false }).then(() => poll());\n }\n\n const snap = api.snapshot();\n const json = JSON.stringify(snap.tree);\n\n if (json === lastJson && json !== '') {\n stableCount++;\n if (stableCount >= idleChecks) {\n return; // Done — scene is idle\n }\n } else {\n stableCount = 0;\n }\n\n lastJson = json;\n return cy.wait(pollIntervalMs, { log: false }).then(() => poll());\n });\n }\n\n return poll();\n },\n );\n}\n","// @react-three-dom/cypress\n// Cypress E2E testing SDK for React Three Fiber apps\n//\n// Usage in cypress/support/e2e.ts:\n// import '@react-three-dom/cypress';\n//\n// This auto-registers all custom commands and Chai assertions.\n\n/// <reference types=\"cypress\" />\n\nimport { registerCommands } from './commands';\nimport { registerAssertions } from './assertions';\nimport { registerWaiters } from './waiters';\n\n// Auto-register on import\nregisterCommands();\nregisterAssertions();\nregisterWaiters();\n\n// Re-export types for programmatic use\nexport type {\n ObjectMetadata,\n ObjectInspection,\n SceneSnapshot,\n SnapshotNode,\n R3FDOM,\n} from './types';\n"]}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@react-three-dom/cypress",
3
+ "version": "0.1.0",
4
+ "description": "Cypress E2E testing SDK for React Three Fiber apps",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": ["dist", "src/index.d.ts"],
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "test": "vitest run",
26
+ "clean": "rm -rf dist"
27
+ },
28
+ "peerDependencies": {
29
+ "cypress": ">=12.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "cypress": "^13",
33
+ "typescript": "^5.7",
34
+ "tsup": "^8",
35
+ "vitest": "^3"
36
+ }
37
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,84 @@
1
+ // ---------------------------------------------------------------------------
2
+ // TypeScript declarations for @react-three-dom/cypress
3
+ // Extends Cypress.Chainable and Cypress.Assertion for full type safety.
4
+ //
5
+ // Usage: add to your cypress/support/e2e.ts:
6
+ // import '@react-three-dom/cypress';
7
+ // ---------------------------------------------------------------------------
8
+
9
+ import type { ObjectMetadata, ObjectInspection, SceneSnapshot } from './types';
10
+
11
+ declare global {
12
+ namespace Cypress {
13
+ interface Chainable {
14
+ // ---- Interactions ----
15
+ /** Click a 3D object by testId or uuid. */
16
+ r3fClick(idOrUuid: string): Chainable<void>;
17
+ /** Double-click a 3D object by testId or uuid. */
18
+ r3fDoubleClick(idOrUuid: string): Chainable<void>;
19
+ /** Right-click / context-menu a 3D object by testId or uuid. */
20
+ r3fContextMenu(idOrUuid: string): Chainable<void>;
21
+ /** Hover over a 3D object by testId or uuid. */
22
+ r3fHover(idOrUuid: string): Chainable<void>;
23
+ /** Drag a 3D object with a world-space delta vector. */
24
+ r3fDrag(idOrUuid: string, delta: { x: number; y: number; z: number }): Chainable<void>;
25
+ /** Dispatch a wheel/scroll event on a 3D object. */
26
+ r3fWheel(idOrUuid: string, options?: { deltaY?: number; deltaX?: number }): Chainable<void>;
27
+ /** Click empty space to trigger onPointerMissed handlers. */
28
+ r3fPointerMiss(): Chainable<void>;
29
+
30
+ // ---- Selection ----
31
+ /** Select a 3D object (highlights in scene). */
32
+ r3fSelect(idOrUuid: string): Chainable<void>;
33
+ /** Clear the current selection. */
34
+ r3fClearSelection(): Chainable<void>;
35
+
36
+ // ---- Queries ----
37
+ /** Get object metadata by testId or uuid. */
38
+ r3fGetObject(idOrUuid: string): Chainable<ObjectMetadata | null>;
39
+ /** Get heavy inspection data (Tier 2) by testId or uuid. */
40
+ r3fInspect(idOrUuid: string): Chainable<ObjectInspection | null>;
41
+ /** Take a full scene snapshot. */
42
+ r3fSnapshot(): Chainable<SceneSnapshot>;
43
+ /** Get the total number of tracked objects. */
44
+ r3fGetCount(): Chainable<number>;
45
+
46
+ // ---- Waiters ----
47
+ /** Wait until the scene is ready (bridge available, object count stable). */
48
+ r3fWaitForSceneReady(options?: {
49
+ stableChecks?: number;
50
+ pollIntervalMs?: number;
51
+ timeout?: number;
52
+ }): Chainable<void>;
53
+ /** Wait until no property changes for N consecutive polls. */
54
+ r3fWaitForIdle(options?: {
55
+ idleChecks?: number;
56
+ pollIntervalMs?: number;
57
+ timeout?: number;
58
+ }): Chainable<void>;
59
+ }
60
+
61
+ interface Assertion {
62
+ /** Assert that a 3D object with the given testId/uuid exists. */
63
+ r3fExist(idOrUuid: string): Assertion;
64
+ /** Assert that a 3D object is visible. */
65
+ r3fVisible(idOrUuid: string): Assertion;
66
+ /** Assert that a 3D object is in the camera frustum. */
67
+ r3fInFrustum(idOrUuid: string): Assertion;
68
+ /** Assert object position within tolerance. */
69
+ r3fPosition(
70
+ idOrUuid: string,
71
+ expected: [number, number, number],
72
+ tolerance?: number,
73
+ ): Assertion;
74
+ /** Assert InstancedMesh instance count. */
75
+ r3fInstanceCount(idOrUuid: string, expectedCount: number): Assertion;
76
+ /** Assert world-space bounding box within tolerance. */
77
+ r3fBounds(
78
+ idOrUuid: string,
79
+ expected: { min: [number, number, number]; max: [number, number, number] },
80
+ tolerance?: number,
81
+ ): Assertion;
82
+ }
83
+ }
84
+ }