@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.
- package/HarnessUI.podspec +20 -0
- package/LICENSE +20 -0
- package/README.md +164 -0
- package/android/build.gradle +77 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/java/com/harnessui/HarnessUIModule.kt +53 -0
- package/android/src/main/java/com/harnessui/HarnessUIPackage.kt +33 -0
- package/android/src/main/java/com/harnessui/UIHelperImpl.kt +335 -0
- package/android/src/main/java/com/harnessui/ViewQueryHelper.kt +135 -0
- package/dist/NativeHarnessUI.d.ts +59 -0
- package/dist/NativeHarnessUI.d.ts.map +1 -0
- package/dist/NativeHarnessUI.js +2 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/screen.d.ts +67 -0
- package/dist/screen.d.ts.map +1 -0
- package/dist/screen.js +76 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/userEvent.d.ts +33 -0
- package/dist/userEvent.d.ts.map +1 -0
- package/dist/userEvent.js +72 -0
- package/ios/HarnessUI.h +5 -0
- package/ios/HarnessUI.mm +526 -0
- package/ios/ViewQueryHelper.h +64 -0
- package/ios/ViewQueryHelper.mm +187 -0
- package/package.json +64 -0
- package/src/NativeHarnessUI.ts +66 -0
- package/src/index.ts +18 -0
- package/src/screen.ts +175 -0
- package/src/userEvent.ts +124 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "HarnessUI"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => "13.0" }
|
|
14
|
+
s.source = { :git => "https://github.com/callstackincubator/react-native-harness.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
|
+
s.private_header_files = "ios/**/*.h"
|
|
18
|
+
|
|
19
|
+
install_modules_dependencies(s)
|
|
20
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Callstack
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
### Native UI Testing Module for React Native Harness
|
|
4
|
+
|
|
5
|
+
[![mit licence][license-badge]][license]
|
|
6
|
+
[![npm downloads][npm-downloads-badge]][npm-downloads]
|
|
7
|
+
[![Chat][chat-badge]][chat]
|
|
8
|
+
[![PRs Welcome][prs-welcome-badge]][prs-welcome]
|
|
9
|
+
|
|
10
|
+
Native UI testing module for React Native Harness that provides view queries and touch simulation capabilities. This module enables finding UI elements and simulating user interactions in your React Native tests.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **View Queries**: Find elements by testID or accessibility label
|
|
15
|
+
- **Touch Simulation**: Simulate user presses and text input
|
|
16
|
+
- **Screenshot Capture**: Capture screenshots of the entire screen or specific elements
|
|
17
|
+
- **Debug-Only**: Automatically excluded from release builds, only available in debug builds
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @react-native-harness/ui
|
|
23
|
+
# or
|
|
24
|
+
pnpm add @react-native-harness/ui
|
|
25
|
+
# or
|
|
26
|
+
yarn add @react-native-harness/ui
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
Import the UI testing utilities in your test files:
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
import { screen, userEvent } from '@react-native-harness/ui';
|
|
35
|
+
|
|
36
|
+
describe('My Component', () => {
|
|
37
|
+
it('should handle user interactions', async () => {
|
|
38
|
+
// Find elements on screen
|
|
39
|
+
const button = await screen.findByTestId('my-button');
|
|
40
|
+
const input = await screen.findByAccessibilityLabel('Username input');
|
|
41
|
+
|
|
42
|
+
// Simulate user interactions
|
|
43
|
+
await userEvent.type(input, 'testuser');
|
|
44
|
+
await userEvent.press(button);
|
|
45
|
+
|
|
46
|
+
// Take screenshots for debugging
|
|
47
|
+
const screenshot = await screen.screenshot();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## API
|
|
53
|
+
|
|
54
|
+
### `screen`
|
|
55
|
+
|
|
56
|
+
Provides methods to query and interact with UI elements on screen.
|
|
57
|
+
|
|
58
|
+
#### `findByTestId(testId: string): Promise<ElementReference>`
|
|
59
|
+
|
|
60
|
+
Finds an element by its testID (accessibilityIdentifier on iOS, tag on Android).
|
|
61
|
+
Throws an error if no element is found.
|
|
62
|
+
|
|
63
|
+
#### `findAllByTestId(testId: string): Promise<ElementReference[]>`
|
|
64
|
+
|
|
65
|
+
Finds all elements by testID. Throws an error if no elements are found.
|
|
66
|
+
|
|
67
|
+
#### `queryByTestId(testId: string): ElementReference | null`
|
|
68
|
+
|
|
69
|
+
Queries for an element by testID without throwing. Returns null if not found.
|
|
70
|
+
|
|
71
|
+
#### `queryAllByTestId(testId: string): ElementReference[]`
|
|
72
|
+
|
|
73
|
+
Queries for all elements by testID without throwing. Returns an empty array if none found.
|
|
74
|
+
|
|
75
|
+
#### `findByAccessibilityLabel(label: string): Promise<ElementReference>`
|
|
76
|
+
|
|
77
|
+
Finds an element by its accessibility label. Throws an error if no element is found.
|
|
78
|
+
|
|
79
|
+
#### `findAllByAccessibilityLabel(label: string): Promise<ElementReference[]>`
|
|
80
|
+
|
|
81
|
+
Finds all elements by accessibility label. Throws an error if no elements are found.
|
|
82
|
+
|
|
83
|
+
#### `queryByAccessibilityLabel(label: string): ElementReference | null`
|
|
84
|
+
|
|
85
|
+
Queries for an element by accessibility label without throwing. Returns null if not found.
|
|
86
|
+
|
|
87
|
+
#### `queryAllByAccessibilityLabel(label: string): ElementReference[]`
|
|
88
|
+
|
|
89
|
+
Queries for all elements by accessibility label without throwing. Returns an empty array if none found.
|
|
90
|
+
|
|
91
|
+
#### `screenshot(element?: ElementReference): Promise<ScreenshotResult | null>`
|
|
92
|
+
|
|
93
|
+
Captures a screenshot of the entire app window or a specific element.
|
|
94
|
+
Returns a ScreenshotResult with PNG data, or null if capture fails.
|
|
95
|
+
|
|
96
|
+
### `userEvent`
|
|
97
|
+
|
|
98
|
+
Provides methods to simulate user interactions.
|
|
99
|
+
|
|
100
|
+
#### `press(element: ElementReference): Promise<void>`
|
|
101
|
+
|
|
102
|
+
Simulates a press on the given element at its center point.
|
|
103
|
+
|
|
104
|
+
#### `pressAt(x: number, y: number): Promise<void>`
|
|
105
|
+
|
|
106
|
+
Simulates a press at the specified screen coordinates.
|
|
107
|
+
|
|
108
|
+
#### `type(element: ElementReference, text: string, options?: TypeOptions): Promise<void>`
|
|
109
|
+
|
|
110
|
+
Simulates typing text into a text input element. Focuses the element, types each character, and blurs the element.
|
|
111
|
+
|
|
112
|
+
**TypeOptions:**
|
|
113
|
+
- `skipPress?: boolean` - If true, pressIn and pressOut events will not be triggered
|
|
114
|
+
- `skipBlur?: boolean` - If true, endEditing and blur events will not be triggered
|
|
115
|
+
- `submitEditing?: boolean` - If true, submitEditing event will be triggered after typing
|
|
116
|
+
|
|
117
|
+
## Types
|
|
118
|
+
|
|
119
|
+
### `ElementReference`
|
|
120
|
+
|
|
121
|
+
Represents an element found on screen with its position and dimensions.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
type ElementReference = {
|
|
125
|
+
x: number;
|
|
126
|
+
y: number;
|
|
127
|
+
width: number;
|
|
128
|
+
height: number;
|
|
129
|
+
// ... additional view info
|
|
130
|
+
};
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### `ScreenshotResult`
|
|
134
|
+
|
|
135
|
+
Screenshot result containing PNG image data.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
interface ScreenshotResult {
|
|
139
|
+
data: Uint8Array; // PNG image data
|
|
140
|
+
width: number; // Width in logical pixels
|
|
141
|
+
height: number; // Height in logical pixels
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Requirements
|
|
146
|
+
|
|
147
|
+
- React Native project with React Native Harness configured
|
|
148
|
+
- This module is only available in debug builds and is automatically excluded from release builds
|
|
149
|
+
|
|
150
|
+
## Made with ❤️ at Callstack
|
|
151
|
+
|
|
152
|
+
`@react-native-harness/ui` is an open source project and will always remain free to use. If you think it's cool, please star it 🌟. [Callstack][callstack-readme-with-love] is a group of React and React Native geeks, contact us at [hello@callstack.com](mailto:hello@callstack.com) if you need any help with these or just want to say hi!
|
|
153
|
+
|
|
154
|
+
Like the project? ⚛️ [Join the team](https://callstack.com/careers/?utm_campaign=Senior_RN&utm_source=github&utm_medium=readme) who does amazing stuff for clients and drives React Native Open Source! 🔥
|
|
155
|
+
|
|
156
|
+
[callstack-readme-with-love]: https://callstack.com/?utm_source=github.com&utm_medium=referral&utm_campaign=react-native-harness&utm_term=readme-with-love
|
|
157
|
+
[license-badge]: https://img.shields.io/npm/l/@react-native-harness/ui?style=for-the-badge
|
|
158
|
+
[license]: https://github.com/callstackincubator/react-native-harness/blob/main/LICENSE
|
|
159
|
+
[npm-downloads-badge]: https://img.shields.io/npm/dm/@react-native-harness/ui?style=for-the-badge
|
|
160
|
+
[npm-downloads]: https://www.npmjs.com/package/@react-native-harness/ui
|
|
161
|
+
[prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge
|
|
162
|
+
[prs-welcome]: ../../CONTRIBUTING.md
|
|
163
|
+
[chat-badge]: https://img.shields.io/discord/426714625279524876.svg?style=for-the-badge
|
|
164
|
+
[chat]: https://discord.gg/xgGt7KAjxv
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.getExtOrDefault = {name ->
|
|
3
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['HarnessUI_' + name]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
13
|
+
// noinspection DifferentKotlinGradleVersion
|
|
14
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
apply plugin: "com.android.library"
|
|
20
|
+
apply plugin: "kotlin-android"
|
|
21
|
+
|
|
22
|
+
apply plugin: "com.facebook.react"
|
|
23
|
+
|
|
24
|
+
def getExtOrIntegerDefault(name) {
|
|
25
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["HarnessUI_" + name]).toInteger()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
android {
|
|
29
|
+
namespace "com.harnessui"
|
|
30
|
+
|
|
31
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
32
|
+
|
|
33
|
+
defaultConfig {
|
|
34
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
35
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
buildFeatures {
|
|
39
|
+
buildConfig true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
buildTypes {
|
|
43
|
+
release {
|
|
44
|
+
minifyEnabled false
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
lintOptions {
|
|
49
|
+
disable "GradleCompatible"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
compileOptions {
|
|
53
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
54
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
sourceSets {
|
|
58
|
+
main {
|
|
59
|
+
java.srcDirs += [
|
|
60
|
+
"generated/java",
|
|
61
|
+
"generated/jni"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
repositories {
|
|
68
|
+
mavenCentral()
|
|
69
|
+
google()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
73
|
+
|
|
74
|
+
dependencies {
|
|
75
|
+
implementation "com.facebook.react:react-android"
|
|
76
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
77
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
package com.harnessui
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Promise
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.ReadableMap
|
|
6
|
+
import com.facebook.react.bridge.WritableArray
|
|
7
|
+
import com.facebook.react.bridge.WritableMap
|
|
8
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* TurboModule for HarnessUI - provides UI testing capabilities.
|
|
12
|
+
* Includes touch simulation and view querying.
|
|
13
|
+
*/
|
|
14
|
+
@ReactModule(name = HarnessUIModule.NAME)
|
|
15
|
+
class HarnessUIModule(reactContext: ReactApplicationContext) :
|
|
16
|
+
NativeHarnessUISpec(reactContext) {
|
|
17
|
+
|
|
18
|
+
companion object {
|
|
19
|
+
const val NAME = "HarnessUI"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private val helper = UIHelperImpl(reactContext)
|
|
23
|
+
|
|
24
|
+
override fun getName(): String = NAME
|
|
25
|
+
|
|
26
|
+
override fun simulatePress(x: Double, y: Double, promise: Promise) {
|
|
27
|
+
helper.simulatePress(x, y, promise)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
override fun queryByTestId(testId: String): WritableMap? =
|
|
31
|
+
helper.queryByTestId(testId)
|
|
32
|
+
|
|
33
|
+
override fun queryByAccessibilityLabel(label: String): WritableMap? =
|
|
34
|
+
helper.queryByAccessibilityLabel(label)
|
|
35
|
+
|
|
36
|
+
override fun queryAllByTestId(testId: String): WritableArray =
|
|
37
|
+
helper.queryAllByTestId(testId)
|
|
38
|
+
|
|
39
|
+
override fun queryAllByAccessibilityLabel(label: String): WritableArray =
|
|
40
|
+
helper.queryAllByAccessibilityLabel(label)
|
|
41
|
+
|
|
42
|
+
override fun captureScreenshot(bounds: ReadableMap?, promise: Promise) {
|
|
43
|
+
helper.captureScreenshot(bounds, promise)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
override fun typeChar(character: String, promise: Promise) {
|
|
47
|
+
helper.typeChar(character, promise)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
override fun blur(options: ReadableMap, promise: Promise) {
|
|
51
|
+
helper.blur(options, promise)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package com.harnessui
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import java.util.HashMap
|
|
9
|
+
|
|
10
|
+
class HarnessUIPackage : BaseReactPackage() {
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
12
|
+
return if (name == HarnessUIModule.NAME) {
|
|
13
|
+
HarnessUIModule(reactContext)
|
|
14
|
+
} else {
|
|
15
|
+
null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
20
|
+
return ReactModuleInfoProvider {
|
|
21
|
+
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
|
|
22
|
+
moduleInfos[HarnessUIModule.NAME] = ReactModuleInfo(
|
|
23
|
+
HarnessUIModule.NAME,
|
|
24
|
+
HarnessUIModule.NAME,
|
|
25
|
+
false, // canOverrideExistingModule
|
|
26
|
+
false, // needsEagerInit
|
|
27
|
+
false, // isCxxModule
|
|
28
|
+
true // isTurboModule
|
|
29
|
+
)
|
|
30
|
+
moduleInfos
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|