@react-native-harness/ui 1.0.0-alpha.22

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.
@@ -0,0 +1,335 @@
1
+ package com.harnessui
2
+
3
+ import android.graphics.Bitmap
4
+ import android.graphics.Canvas
5
+ import android.os.Handler
6
+ import android.os.Looper
7
+ import android.os.SystemClock
8
+ import android.util.Log
9
+ import android.view.MotionEvent
10
+ import android.view.inputmethod.EditorInfo
11
+ import android.view.inputmethod.InputMethodManager
12
+ import android.content.Context
13
+ import android.widget.EditText
14
+ import com.facebook.react.bridge.Arguments
15
+ import com.facebook.react.bridge.Promise
16
+ import com.facebook.react.bridge.ReactApplicationContext
17
+ import com.facebook.react.bridge.ReadableMap
18
+ import com.facebook.react.bridge.UiThreadUtil
19
+ import com.facebook.react.bridge.WritableArray
20
+ import com.facebook.react.bridge.WritableMap
21
+ import com.facebook.react.bridge.WritableNativeArray
22
+ import java.io.ByteArrayOutputStream
23
+ import java.nio.ByteBuffer
24
+ import java.util.concurrent.CountDownLatch
25
+ import java.util.concurrent.TimeUnit
26
+
27
+ /**
28
+ * UI helper implementation for HarnessUI.
29
+ * Includes touch simulation and view querying capabilities.
30
+ */
31
+ class UIHelperImpl(private val context: ReactApplicationContext) {
32
+
33
+ companion object {
34
+ private const val TAG = "HarnessUI"
35
+ private const val TAP_DURATION_MS = 50L // Duration between touch down and up
36
+ private const val EVENT_PROCESSING_DELAY_MS = 10L // Delay after touch up for React Native to process the event
37
+ }
38
+
39
+ private val mainHandler = Handler(Looper.getMainLooper())
40
+
41
+ // =========================================================================
42
+ // Touch Simulation
43
+ // =========================================================================
44
+
45
+ fun simulatePress(x: Double, y: Double, promise: Promise) {
46
+ Log.i(TAG, "simulatePress called with x:$x y:$y")
47
+
48
+ UiThreadUtil.runOnUiThread {
49
+ val activity = context.currentActivity ?: run {
50
+ Log.w(TAG, "No current activity")
51
+ promise.resolve(null)
52
+ return@runOnUiThread
53
+ }
54
+ val root = activity.window.decorView
55
+
56
+ // Convert DP to PX
57
+ val density = root.resources.displayMetrics.density
58
+ val pxX = (x * density).toFloat()
59
+ val pxY = (y * density).toFloat()
60
+
61
+ val downTime = SystemClock.uptimeMillis()
62
+
63
+ // 1. ACTION_DOWN
64
+ val downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, pxX, pxY, 0)
65
+ try {
66
+ root.dispatchTouchEvent(downEvent)
67
+ Log.i(TAG, "Sent touch down at ($pxX, $pxY)")
68
+ } finally {
69
+ downEvent.recycle()
70
+ }
71
+
72
+ // 2. ACTION_UP after real delay to allow press feedback to render
73
+ mainHandler.postDelayed({
74
+ val upTime = SystemClock.uptimeMillis()
75
+ val upEvent = MotionEvent.obtain(downTime, upTime, MotionEvent.ACTION_UP, pxX, pxY, 0)
76
+ try {
77
+ root.dispatchTouchEvent(upEvent)
78
+ Log.i(TAG, "Tap completed at ($pxX, $pxY)")
79
+ } finally {
80
+ upEvent.recycle()
81
+ }
82
+ // Wait for React Native to process the touch event and trigger JS callbacks
83
+ mainHandler.postDelayed({
84
+ promise.resolve(null)
85
+ }, EVENT_PROCESSING_DELAY_MS)
86
+ }, TAP_DURATION_MS)
87
+ }
88
+ }
89
+
90
+ // =========================================================================
91
+ // Query API
92
+ // =========================================================================
93
+
94
+ fun queryByTestId(testId: String): WritableMap? {
95
+ Log.i(TAG, "queryByTestId called with: $testId")
96
+ return executeQuery(ViewQueryType.TEST_ID, testId)
97
+ }
98
+
99
+ fun queryByAccessibilityLabel(label: String): WritableMap? {
100
+ Log.i(TAG, "queryByAccessibilityLabel called with: $label")
101
+ return executeQuery(ViewQueryType.ACCESSIBILITY_LABEL, label)
102
+ }
103
+
104
+ fun queryAllByTestId(testId: String): WritableArray {
105
+ Log.i(TAG, "queryAllByTestId called with: $testId")
106
+ return executeQueryAll(ViewQueryType.TEST_ID, testId)
107
+ }
108
+
109
+ fun queryAllByAccessibilityLabel(label: String): WritableArray {
110
+ Log.i(TAG, "queryAllByAccessibilityLabel called with: $label")
111
+ return executeQueryAll(ViewQueryType.ACCESSIBILITY_LABEL, label)
112
+ }
113
+
114
+ /**
115
+ * Executes a query on the UI thread and returns the result.
116
+ * Uses CountDownLatch to synchronize with the UI thread.
117
+ */
118
+ private fun executeQuery(queryType: ViewQueryType, value: String): WritableMap? {
119
+ var result: WritableMap? = null
120
+
121
+ // If already on UI thread, execute directly
122
+ if (UiThreadUtil.isOnUiThread()) {
123
+ val activity = context.currentActivity ?: return null
124
+ result = ViewQueryHelper.query(activity, queryType, value)?.toWritableMap()
125
+ } else {
126
+ // Execute on UI thread and wait for result
127
+ val latch = CountDownLatch(1)
128
+
129
+ UiThreadUtil.runOnUiThread {
130
+ try {
131
+ val activity = context.currentActivity
132
+ if (activity != null) {
133
+ result = ViewQueryHelper.query(activity, queryType, value)?.toWritableMap()
134
+ }
135
+ } finally {
136
+ latch.countDown()
137
+ }
138
+ }
139
+
140
+ // Wait for UI thread with timeout
141
+ try {
142
+ latch.await(5, TimeUnit.SECONDS)
143
+ } catch (e: InterruptedException) {
144
+ Log.e(TAG, "Query interrupted", e)
145
+ }
146
+ }
147
+
148
+ Log.i(TAG, "Query result: $result")
149
+ return result
150
+ }
151
+
152
+ /**
153
+ * Executes a query for all matching views on the UI thread.
154
+ * Uses CountDownLatch to synchronize with the UI thread.
155
+ */
156
+ private fun executeQueryAll(queryType: ViewQueryType, value: String): WritableArray {
157
+ var result: WritableArray = Arguments.createArray()
158
+
159
+ // If already on UI thread, execute directly
160
+ if (UiThreadUtil.isOnUiThread()) {
161
+ val activity = context.currentActivity ?: return result
162
+ val queryResults = ViewQueryHelper.queryAll(activity, queryType, value)
163
+ result = Arguments.createArray().apply {
164
+ queryResults.forEach { pushMap(it.toWritableMap()) }
165
+ }
166
+ } else {
167
+ // Execute on UI thread and wait for result
168
+ val latch = CountDownLatch(1)
169
+
170
+ UiThreadUtil.runOnUiThread {
171
+ try {
172
+ val activity = context.currentActivity
173
+ if (activity != null) {
174
+ val queryResults = ViewQueryHelper.queryAll(activity, queryType, value)
175
+ result = Arguments.createArray().apply {
176
+ queryResults.forEach { pushMap(it.toWritableMap()) }
177
+ }
178
+ }
179
+ } finally {
180
+ latch.countDown()
181
+ }
182
+ }
183
+
184
+ // Wait for UI thread with timeout
185
+ try {
186
+ latch.await(5, TimeUnit.SECONDS)
187
+ } catch (e: InterruptedException) {
188
+ Log.e(TAG, "QueryAll interrupted", e)
189
+ }
190
+ }
191
+
192
+ Log.i(TAG, "QueryAll result count: ${result.size()}")
193
+ return result
194
+ }
195
+
196
+ // =========================================================================
197
+ // Screenshot Capture
198
+ // =========================================================================
199
+
200
+ fun captureScreenshot(bounds: ReadableMap?, promise: Promise) {
201
+ Log.i(TAG, "captureScreenshot called")
202
+
203
+ UiThreadUtil.runOnUiThread {
204
+ val activity = context.currentActivity ?: run {
205
+ Log.w(TAG, "No current activity")
206
+ promise.resolve(null)
207
+ return@runOnUiThread
208
+ }
209
+
210
+ val root = activity.window.decorView.rootView
211
+ val density = root.resources.displayMetrics.density
212
+
213
+ try {
214
+ // Determine capture dimensions
215
+ val captureX: Int
216
+ val captureY: Int
217
+ val captureWidth: Int
218
+ val captureHeight: Int
219
+
220
+ // Check if bounds are provided and have non-zero dimensions
221
+ val hasBounds = bounds != null && bounds.getDouble("width") > 0 && bounds.getDouble("height") > 0
222
+
223
+ if (hasBounds) {
224
+ // Convert dp to px for bounds
225
+ captureX = (bounds!!.getDouble("x") * density).toInt()
226
+ captureY = (bounds.getDouble("y") * density).toInt()
227
+ captureWidth = (bounds.getDouble("width") * density).toInt()
228
+ captureHeight = (bounds.getDouble("height") * density).toInt()
229
+ Log.i(TAG, "Capturing region: x=$captureX y=$captureY w=$captureWidth h=$captureHeight")
230
+ } else {
231
+ captureX = 0
232
+ captureY = 0
233
+ captureWidth = root.width
234
+ captureHeight = root.height
235
+ Log.i(TAG, "Capturing full window: w=$captureWidth h=$captureHeight")
236
+ }
237
+
238
+ // Create bitmap and canvas
239
+ val bitmap = Bitmap.createBitmap(captureWidth, captureHeight, Bitmap.Config.ARGB_8888)
240
+ val canvas = Canvas(bitmap)
241
+
242
+ // Translate canvas if capturing a specific region
243
+ canvas.translate(-captureX.toFloat(), -captureY.toFloat())
244
+
245
+ // Draw the view hierarchy
246
+ root.draw(canvas)
247
+
248
+ // Convert to PNG bytes
249
+ val outputStream = ByteArrayOutputStream()
250
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
251
+ val pngBytes = outputStream.toByteArray()
252
+
253
+ // Clean up
254
+ bitmap.recycle()
255
+
256
+ // Return as Base64 string for efficiency
257
+ val base64String = android.util.Base64.encodeToString(pngBytes, android.util.Base64.NO_WRAP)
258
+ Log.i(TAG, "Screenshot captured successfully (${pngBytes.size} bytes)")
259
+ promise.resolve(base64String)
260
+ } catch (e: Exception) {
261
+ Log.e(TAG, "Screenshot capture failed", e)
262
+ promise.resolve(null)
263
+ }
264
+ }
265
+ }
266
+
267
+ // =========================================================================
268
+ // Text Input
269
+ // =========================================================================
270
+
271
+ fun typeChar(character: String, promise: Promise) {
272
+ Log.i(TAG, "typeChar called with: $character")
273
+
274
+ UiThreadUtil.runOnUiThread {
275
+ val activity = context.currentActivity ?: run {
276
+ Log.w(TAG, "No current activity")
277
+ promise.resolve(null)
278
+ return@runOnUiThread
279
+ }
280
+
281
+ val focused = activity.currentFocus
282
+ if (focused is EditText) {
283
+ // Insert character at cursor position
284
+ val start = focused.selectionStart
285
+ val end = focused.selectionEnd
286
+ focused.text.replace(start, end, character)
287
+ Log.i(TAG, "Inserted character: $character")
288
+ } else {
289
+ Log.i(TAG, "No EditText is focused")
290
+ }
291
+
292
+ // Delay for React Native to process onChangeText
293
+ mainHandler.postDelayed({
294
+ promise.resolve(null)
295
+ }, EVENT_PROCESSING_DELAY_MS)
296
+ }
297
+ }
298
+
299
+ fun blur(options: ReadableMap, promise: Promise) {
300
+ val submitEditing = options.getBoolean("submitEditing")
301
+ Log.i(TAG, "blur called, submitEditing: $submitEditing")
302
+
303
+ UiThreadUtil.runOnUiThread {
304
+ val activity = context.currentActivity ?: run {
305
+ Log.w(TAG, "No current activity")
306
+ promise.resolve(null)
307
+ return@runOnUiThread
308
+ }
309
+
310
+ val focused = activity.currentFocus
311
+
312
+ if (submitEditing && focused is EditText) {
313
+ Log.i(TAG, "Triggering submitEditing")
314
+ // Trigger onSubmitEditing by simulating IME action
315
+ focused.onEditorAction(EditorInfo.IME_ACTION_DONE)
316
+ }
317
+
318
+ if (focused != null) {
319
+ // Clear focus (triggers onEndEditing and onBlur)
320
+ Log.i(TAG, "Clearing focus")
321
+ focused.clearFocus()
322
+
323
+ // Hide keyboard
324
+ val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
325
+ imm.hideSoftInputFromWindow(focused.windowToken, 0)
326
+ } else {
327
+ Log.i(TAG, "No view is focused")
328
+ }
329
+
330
+ mainHandler.postDelayed({
331
+ promise.resolve(null)
332
+ }, EVENT_PROCESSING_DELAY_MS)
333
+ }
334
+ }
335
+ }
@@ -0,0 +1,135 @@
1
+ package com.harnessui
2
+
3
+ import android.app.Activity
4
+ import android.view.View
5
+ import android.view.ViewGroup
6
+ import com.facebook.react.bridge.Arguments
7
+ import com.facebook.react.bridge.WritableMap
8
+
9
+ /**
10
+ * Enum defining the types of queries that can be performed on the view hierarchy.
11
+ */
12
+ enum class ViewQueryType {
13
+ TEST_ID,
14
+ ACCESSIBILITY_LABEL
15
+ }
16
+
17
+ /**
18
+ * Represents information about a found view.
19
+ */
20
+ data class ViewQueryResult(
21
+ val x: Float,
22
+ val y: Float,
23
+ val width: Float,
24
+ val height: Float
25
+ ) {
26
+ fun toWritableMap(): WritableMap {
27
+ return Arguments.createMap().apply {
28
+ putDouble("x", x.toDouble())
29
+ putDouble("y", y.toDouble())
30
+ putDouble("width", width.toDouble())
31
+ putDouble("height", height.toDouble())
32
+ }
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Helper object for querying the view hierarchy.
38
+ * Provides reusable query logic for finding views by various criteria.
39
+ */
40
+ object ViewQueryHelper {
41
+
42
+ /**
43
+ * Finds the first view matching the query criteria.
44
+ * @param activity The current activity.
45
+ * @param queryType The type of query to perform.
46
+ * @param value The value to match against.
47
+ * @return ViewQueryResult if found, null otherwise.
48
+ */
49
+ fun query(activity: Activity, queryType: ViewQueryType, value: String): ViewQueryResult? {
50
+ val root = activity.window.decorView
51
+ val density = root.resources.displayMetrics.density
52
+ val found = findViewInView(root, queryType, value) ?: return null
53
+ return resultFromView(found, density)
54
+ }
55
+
56
+ /**
57
+ * Finds all views matching the query criteria.
58
+ * @param activity The current activity.
59
+ * @param queryType The type of query to perform.
60
+ * @param value The value to match against.
61
+ * @return List of ViewQueryResult objects.
62
+ */
63
+ fun queryAll(activity: Activity, queryType: ViewQueryType, value: String): List<ViewQueryResult> {
64
+ val root = activity.window.decorView
65
+ val density = root.resources.displayMetrics.density
66
+ val views = mutableListOf<View>()
67
+ findAllViewsInView(root, queryType, value, views)
68
+ return views.map { resultFromView(it, density) }
69
+ }
70
+
71
+ /**
72
+ * Checks if a view matches the given query criteria.
73
+ */
74
+ private fun viewMatches(view: View, queryType: ViewQueryType, value: String): Boolean {
75
+ return when (queryType) {
76
+ ViewQueryType.TEST_ID -> view.tag == value
77
+ ViewQueryType.ACCESSIBILITY_LABEL -> view.contentDescription?.toString() == value
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Recursively finds the first view matching the query criteria.
83
+ */
84
+ private fun findViewInView(view: View, queryType: ViewQueryType, value: String): View? {
85
+ if (viewMatches(view, queryType, value)) {
86
+ return view
87
+ }
88
+
89
+ if (view is ViewGroup) {
90
+ for (i in 0 until view.childCount) {
91
+ val found = findViewInView(view.getChildAt(i), queryType, value)
92
+ if (found != null) {
93
+ return found
94
+ }
95
+ }
96
+ }
97
+ return null
98
+ }
99
+
100
+ /**
101
+ * Recursively finds all views matching the query criteria.
102
+ */
103
+ private fun findAllViewsInView(
104
+ view: View,
105
+ queryType: ViewQueryType,
106
+ value: String,
107
+ results: MutableList<View>
108
+ ) {
109
+ if (viewMatches(view, queryType, value)) {
110
+ results.add(view)
111
+ }
112
+
113
+ if (view is ViewGroup) {
114
+ for (i in 0 until view.childCount) {
115
+ findAllViewsInView(view.getChildAt(i), queryType, value, results)
116
+ }
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Converts a View to a ViewQueryResult with screen coordinates in dp.
122
+ */
123
+ private fun resultFromView(view: View, density: Float): ViewQueryResult {
124
+ val location = IntArray(2)
125
+ view.getLocationOnScreen(location)
126
+
127
+ // Convert pixels to dp
128
+ val x = location[0] / density
129
+ val y = location[1] / density
130
+ val width = view.width / density
131
+ val height = view.height / density
132
+
133
+ return ViewQueryResult(x, y, width, height)
134
+ }
135
+ }
@@ -0,0 +1,59 @@
1
+ import { type TurboModule } from 'react-native';
2
+ /**
3
+ * Represents the position and dimensions of a view in screen coordinates (points/dp).
4
+ */
5
+ export interface ViewInfo {
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ }
11
+ interface Spec extends TurboModule {
12
+ /**
13
+ * Simulates a native press at the specified screen coordinates.
14
+ * Returns a promise that resolves when the press action is complete.
15
+ */
16
+ simulatePress(x: number, y: number): Promise<void>;
17
+ /**
18
+ * Finds a view by its testID (accessibilityIdentifier on iOS, tag on Android).
19
+ * Returns null if no view is found.
20
+ */
21
+ queryByTestId(testId: string): ViewInfo | null;
22
+ /**
23
+ * Finds all views by testID (accessibilityIdentifier on iOS, tag on Android).
24
+ * Returns an empty array if no views are found.
25
+ */
26
+ queryAllByTestId(testId: string): ViewInfo[];
27
+ /**
28
+ * Finds a view by its accessibility label.
29
+ * Returns null if no view is found.
30
+ */
31
+ queryByAccessibilityLabel(label: string): ViewInfo | null;
32
+ /**
33
+ * Finds all views by accessibility label.
34
+ * Returns an empty array if no views are found.
35
+ */
36
+ queryAllByAccessibilityLabel(label: string): ViewInfo[];
37
+ /**
38
+ * Captures a screenshot of the app UI.
39
+ * @param bounds Optional bounds to capture a specific region. Pass null to capture the entire window.
40
+ * @returns Promise resolving to Base64 encoded string containing PNG data, or null on failure.
41
+ */
42
+ captureScreenshot(bounds: ViewInfo | null): Promise<string | null>;
43
+ /**
44
+ * Types a single character into the currently focused text input.
45
+ * If no text input is focused, this is a no-op.
46
+ */
47
+ typeChar(character: string): Promise<void>;
48
+ /**
49
+ * Blurs (resigns first responder from) the currently focused element.
50
+ * Optionally triggers submitEditing event before blur.
51
+ */
52
+ blur(options: {
53
+ submitEditing?: boolean;
54
+ }): Promise<void>;
55
+ }
56
+ export type HarnessUIModule = Spec;
57
+ declare const _default: Spec;
58
+ export default _default;
59
+ //# sourceMappingURL=NativeHarnessUI.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeHarnessUI.d.ts","sourceRoot":"","sources":["../src/NativeHarnessUI.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,IAAK,SAAQ,WAAW;IAChC;;;OAGG;IACH,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnD;;;OAGG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC;IAE/C;;;OAGG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,EAAE,CAAC;IAE7C;;;OAGG;IACH,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC;IAE1D;;;OAGG;IACH,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,EAAE,CAAC;IAExD;;;;OAIG;IACH,iBAAiB,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAEnE;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3C;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC;;AAEnC,wBAAmE"}
@@ -0,0 +1,2 @@
1
+ import { TurboModuleRegistry } from 'react-native';
2
+ export default TurboModuleRegistry.getEnforcing('HarnessUI');
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @react-native-harness/ui
3
+ *
4
+ * Native UI testing module for React Native Harness.
5
+ * Provides view queries and touch simulation.
6
+ *
7
+ * This module is automatically excluded from release builds
8
+ * and only available in debug builds.
9
+ */
10
+ export { screen, type Screen, type ElementReference, type ScreenshotResult, } from './screen.js';
11
+ export { userEvent, type UserEvent } from './userEvent.js';
12
+ export type { ViewInfo } from './NativeHarnessUI.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,MAAM,EACN,KAAK,MAAM,EACX,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3D,YAAY,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @react-native-harness/ui
3
+ *
4
+ * Native UI testing module for React Native Harness.
5
+ * Provides view queries and touch simulation.
6
+ *
7
+ * This module is automatically excluded from release builds
8
+ * and only available in debug builds.
9
+ */
10
+ export { screen, } from './screen.js';
11
+ export { userEvent } from './userEvent.js';
@@ -0,0 +1,67 @@
1
+ import { type ViewInfo } from './NativeHarnessUI.js';
2
+ /**
3
+ * Represents an element found on screen with its position and dimensions.
4
+ * This can be used with userEvent.press() to interact with the element.
5
+ */
6
+ export type ElementReference = ViewInfo;
7
+ /**
8
+ * Screenshot result containing PNG image data.
9
+ */
10
+ export interface ScreenshotResult {
11
+ /** PNG image data as Uint8Array (ArrayBuffer view) */
12
+ data: Uint8Array;
13
+ /** Width of the captured image in logical pixels (points/dp) */
14
+ width: number;
15
+ /** Height of the captured image in logical pixels (points/dp) */
16
+ height: number;
17
+ }
18
+ export type Screen = {
19
+ /**
20
+ * Finds an element by its testID (accessibilityIdentifier on iOS, tag on Android).
21
+ * @throws Error if no element is found with the given testID.
22
+ */
23
+ findByTestId: (testId: string) => Promise<ElementReference>;
24
+ /**
25
+ * Finds all elements by testID (accessibilityIdentifier on iOS, tag on Android).
26
+ * @throws Error if no elements are found with the given testID.
27
+ */
28
+ findAllByTestId: (testId: string) => Promise<ElementReference[]>;
29
+ /**
30
+ * Queries for an element by its testID without throwing.
31
+ * Returns null if no element is found.
32
+ */
33
+ queryByTestId: (testId: string) => ElementReference | null;
34
+ /**
35
+ * Queries for all elements by testID without throwing.
36
+ * Returns an empty array if no elements are found.
37
+ */
38
+ queryAllByTestId: (testId: string) => ElementReference[];
39
+ /**
40
+ * Finds an element by its accessibility label.
41
+ * @throws Error if no element is found with the given label.
42
+ */
43
+ findByAccessibilityLabel: (label: string) => Promise<ElementReference>;
44
+ /**
45
+ * Finds all elements by accessibility label.
46
+ * @throws Error if no elements are found with the given label.
47
+ */
48
+ findAllByAccessibilityLabel: (label: string) => Promise<ElementReference[]>;
49
+ /**
50
+ * Queries for an element by its accessibility label without throwing.
51
+ * Returns null if no element is found.
52
+ */
53
+ queryByAccessibilityLabel: (label: string) => ElementReference | null;
54
+ /**
55
+ * Queries for all elements by accessibility label without throwing.
56
+ * Returns an empty array if no elements are found.
57
+ */
58
+ queryAllByAccessibilityLabel: (label: string) => ElementReference[];
59
+ /**
60
+ * Captures a screenshot of the entire app window or a specific element.
61
+ * @param element Optional element reference to capture. If not provided, captures the entire window.
62
+ * @returns Promise resolving to ScreenshotResult with PNG data, or null if capture fails.
63
+ */
64
+ screenshot: (element?: ElementReference) => Promise<ScreenshotResult | null>;
65
+ };
66
+ export declare const screen: Screen;
67
+ //# sourceMappingURL=screen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../src/screen.ts"],"names":[],"mappings":"AAAA,OAAwB,EAAE,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGtE;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;AAExC;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sDAAsD;IACtD,IAAI,EAAE,UAAU,CAAC;IACjB,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,MAAM,GAAG;IACnB;;;OAGG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAE5D;;;OAGG;IACH,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAEjE;;;OAGG;IACH,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,gBAAgB,GAAG,IAAI,CAAC;IAE3D;;;OAGG;IACH,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,gBAAgB,EAAE,CAAC;IAEzD;;;OAGG;IACH,wBAAwB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEvE;;;OAGG;IACH,2BAA2B,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAE5E;;;OAGG;IACH,yBAAyB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,gBAAgB,GAAG,IAAI,CAAC;IAEtE;;;OAGG;IACH,4BAA4B,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,gBAAgB,EAAE,CAAC;IAEpE;;;;OAIG;IACH,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CAC9E,CAAC;AAkGF,eAAO,MAAM,MAAM,QAAiB,CAAC"}