@@ -112,7 +112,7 @@ export function getTestData() {
112112}
113113
114114export async function deleteUser ( externalId : string ) {
115- console . log ( `Deleting user: ${ externalId } ` ) ;
115+ console . info ( `Deleting user: ${ externalId } ` ) ;
116116 try {
117117 const response = await fetch (
118118 `https://api.onesignal.com/apps/${ process . env . ONESIGNAL_APP_ID } /users/by/external_id/${ externalId } ` ,
@@ -127,12 +127,21 @@ export async function deleteUser(externalId: string) {
127127 if ( ! response . ok ) {
128128 throw new Error ( `Failed to delete user: ${ response . statusText } ` ) ;
129129 }
130- console . log ( `User deleted successfully` ) ;
130+ console . info ( `User deleted successfully` ) ;
131131 } catch ( error ) {
132132 console . error ( `Failed to delete user: ${ error } ` ) ;
133133 }
134134}
135135
136+ export async function getToggleState ( el : {
137+ getAttribute ( name : string ) : Promise < string | null > ;
138+ } ) : Promise < boolean > {
139+ if ( getPlatform ( ) === 'ios' ) {
140+ return ( await el . getAttribute ( 'value' ) ) === '1' ;
141+ }
142+ return ( await el . getAttribute ( 'checked' ) ) === 'true' ;
143+ }
144+
136145export function getSdkType ( ) : SdkType {
137146 const sdkType = process . env . SDK_TYPE ;
138147 if ( sdkType && VALID_SDK_TYPES . has ( sdkType ) ) {
@@ -143,28 +152,70 @@ export function getSdkType(): SdkType {
143152 ) ;
144153}
145154
155+ /**
156+ * On Flutter Android, the standard WebDriver getText() often returns empty
157+ * because Flutter writes text into content-desc / text attributes rather than
158+ * the property that UiAutomator2's getText maps to. This proxy intercepts
159+ * getText() and falls back to those attributes.
160+ */
161+ function withFlutterAndroidFixes < T extends { getText ( ) : Promise < string > } > ( el : T ) : T {
162+ if ( ! ( getPlatform ( ) === 'android' && getSdkType ( ) === 'flutter' ) ) {
163+ return el ;
164+ }
165+
166+ return new Proxy ( el , {
167+ get ( target , prop , receiver ) {
168+ if ( prop === 'getText' ) {
169+ return async ( ) => {
170+ const text = ( await target . getText ( ) ) . trim ( ) ;
171+ if ( text ) return text ;
172+
173+ const attrs = [ 'content-desc' , 'contentDescription' , 'text' , 'name' ] ;
174+ for ( const attr of attrs ) {
175+ try {
176+ const val = (
177+ await (
178+ target as unknown as { getAttribute ( n : string ) : Promise < string | null > }
179+ ) . getAttribute ( attr )
180+ ) ?. trim ( ) ;
181+ if ( val ) return val ;
182+ } catch {
183+ /* best-effort */
184+ }
185+ }
186+
187+ return '' ;
188+ } ;
189+ }
190+
191+ const value = Reflect . get ( target , prop , receiver ) ;
192+ if ( typeof value === 'function' ) {
193+ return value . bind ( target ) ;
194+ }
195+
196+ return value ;
197+ } ,
198+ } ) ;
199+ }
200+
146201/**
147202 * Select an element by its cross-platform test ID.
148203 *
149204 * Native iOS uses `accessibilityIdentifier`, native Android Compose uses
150- * `testTag`, RN uses `testID`, and Flutter uses `Semantics(label:)` — all
151- * map to Appium accessibility id. Capacitor uses `data-testid` as a CSS
152- * attribute inside a WebView.
205+ * `testTag`, RN uses `testID` — all map to Appium accessibility id (`~`).
206+ * Flutter uses `Semantics(identifier:)` which maps to `accessibilityIdentifier`
207+ * on iOS (`~`) but to `resource-id` on Android (UiAutomator selector).
208+ * Capacitor uses `data-testid` as a CSS attribute inside a WebView.
153209 */
154210export async function byTestId ( id : string ) {
155211 const sdkType = getSdkType ( ) ;
156- switch ( sdkType ) {
157- case 'react-native' :
158- case 'flutter' :
159- case 'unity' :
160- case 'cordova' :
161- case 'dotnet' :
162- case 'ios' :
163- case 'android' :
164- return $ ( `~${ id } ` ) ;
165- case 'capacitor' :
166- return $ ( `[data-testid="${ id } "]` ) ;
167- }
212+ const platform = getPlatform ( ) ;
213+
214+ if ( sdkType === 'capacitor' ) return $ ( `[data-testid="${ id } "]` ) ;
215+ if ( sdkType === 'flutter' && platform === 'android' )
216+ return withFlutterAndroidFixes ( await $ ( `id=${ id } ` ) ) ;
217+
218+ return $ ( `~${ id } ` ) ;
168219}
169220
170221/**
@@ -173,16 +224,17 @@ export async function byTestId(id: string) {
173224 */
174225export async function byText ( text : string , partial = false ) {
175226 const platform = getPlatform ( ) ;
176- const sdkType = getSdkType ( ) ;
177-
178- if ( sdkType === 'capacitor' ) {
179- return $ ( `//*[contains(text(), "${ text } ")]` ) ;
180- }
181227
182228 if ( platform === 'ios' ) {
183229 const op = partial ? 'CONTAINS' : '==' ;
184230 return $ ( `-ios predicate string:label ${ op } "${ text } "` ) ;
185231 }
186- const method = partial ? 'textContains' : 'text' ;
187- return $ ( `android=new UiSelector().${ method } ("${ text } ")` ) ;
232+
233+ if ( partial ) {
234+ return withFlutterAndroidFixes (
235+ await $ ( `//*[contains(@content-desc, "${ text } ") or contains(@text, "${ text } ")]` ) ,
236+ ) ;
237+ }
238+
239+ return withFlutterAndroidFixes ( await $ ( `//*[@content-desc="${ text } " or @text="${ text } "]` ) ) ;
188240}
0 commit comments