Author: Taha Paksu

  • Detecting Maestro in Expo application

    Detecting Maestro in Expo application

    While running E2E tests with Maestro locally (or in Cloud/GH Actions, didn’t try that yet), you sometimes may need to detect if Maestro is running on your application, to modify things to be less intrusive in testing, such as the secure password modal shown on iOS simulator. I was looking a way to skip this modal to be able to enter a password automatically on my test, and I jumped into the rabbit hole for detecting Maestro in the app to disable the autocomplete feature of the password inputs.

    When you first skim through the https://docs.maestro.dev/advanced/detect-maestro-in-your-app#mobile doc, it suggests you to create some native code to check the ports (port 22087 on iOs, 7001 on Android) if they are being used to see if Maestro is running, but the page also has a notice saying that this method is deprecated, and discouraged. The actual answer lies between the lines, with a link sending you to this launch arguments page, where you should set some arguments, and check them in React like this:

    YAML code that launches the app:

    - launchApp:
        arguments: 
           isMaestroRunning: true

    React code that checks that argument:

    import { LaunchArguments } from 'react-native-launch-arguments';
    
    export const isMaestroRunning = (): boolean => {
      try {
        const isMaestroRunning = LaunchArguments.value().isMaestroRunning;
        return !!isMaestroRunning;
      } catch {
        return false;
      }
    };

    But as you may already noticed, that requires you to install react-native-launch-arguments package in your app, with:

    npm install react-native-launch-arguments

    Without using any other package, or checking any ports, you can use these code to figure out if your app is running on Maestro, or not.

    In my use case of making password fields less intrusive on iOS simulator, I used the above code like this:

    import React from 'react';
    
    import { Platform, TextInput, TextInputProps } from 'react-native';
    import { omit } from 'lodash';
    import { isMaestroRunning } from '@/src/utils/maestro';
    
    export default function ManagedTextInput({
      errorMessage,
      successMessage,
      innerRef,
      ...props
    }: {
      errorMessage?: string | null;
      successMessage?: string | null;
      innerRef?: React.Ref<TextInput>;
    } & TextInputProps) {
    
      const disableAutofill = isMaestroRunning() && Platform.OS === 'ios';
    
      const isPassword =
        props.textContentType === 'password' ||
        props.textContentType === 'newPassword';
    
      const finalTextContentType = disableAutofill ? 'none' : props.textContentType;
      const finalAutoComplete = disableAutofill ? 'off' : props.autoComplete;
      const finalImportantForAutofill = disableAutofill
        ? 'no'
        : props.importantForAutofill;
    
      return (
        <>
          <TextInput
            ref={innerRef}
            className={[
              'flex flex-row items-center justify-center font-medium',
              errorMessage ? 'border-red-500' : '',
              successMessage ? 'border-green-500' : '',
              props.className,
            ].join(' ')}
            placeholderTextColor={'slate'}
            style={[
              {
                backgroundColor: 'white',
                color: 'black',
                borderColor: errorMessage
                  ? 'red'
                  : successMessage
                  ? '#22c55e'
                  : 'silver',
                borderWidth: 1,
              },
              props.style,
            ]}
            {...omit(props, [
              'style',
              'className',
              'textContentType',
              'autoComplete',
              'importantForAutofill',
              'secureTextEntry',
              'passwordRules',
            ])}
            secureTextEntry={isPassword}
            textContentType={finalTextContentType}
            autoComplete={finalAutoComplete}
            importantForAutofill={finalImportantForAutofill}
            passwordRules={disableAutofill ? '' : props.passwordRules}
          />
          {errorMessage && (
            <Text
              className="text-sm mb-3 -mt-1"
              style={{ color: 'red' }}
            >
              {errorMessage}
            </Text>
          )}
          {successMessage && !errorMessage && (
            <Text
              className="text-sm mb-3 -mt-1"
              style={{ color: '#22c55e' }}
            >
              {successMessage}
            </Text>
          )}
        </>
      );
    }
    

    The code above is reduced to show the bits that got affected by whether Maestro is running or not, so may not be directly running. But I believe you got the idea.

    Happy coding!

    Edit: This only seems to work in old architecture, but there are PR’s pending to adapt react-native-launch-arguments package to new architecture. Until that, you can check https://github.com/kievu/expo-native-launch-arguments which is a fork of the package, and adds the new architecture support. But beware, this repository and package may get removed once the original package adds support for new arch.

  • Debugging WebViews on IOS Simulator

    Debugging WebViews on IOS Simulator

    Something I found out recently while trying to get console.log messages out of webviews on React Native apps, turns out I never needed a custom onMessage event handler, and a script hijacking the console methods to replace them with postMessage’s instead. Guess what? Safari can connect to webviews and let you do your developer things on them! Let’s put the excitement aside, and explain this step by step:

    1. Let developer tools menu always be visible on the Safari menubar, by activating “Show features for web developers” checkbox
    1. Open the screen containing the webview you want to debug on IOS simulator
    1. Click Safari to make its menu visible on the top menubar
    1. Click Developer > Your simulator name > The webview title

    VoilĂ ! You just have your debugger attached to that nasty webview that hides its secrets from you!

    Big kudos to this person answering the question, and his friend telling him how to do it here: https://stackoverflow.com/a/20233054/916000

    Happy coding!