diff --git a/.argent/flows/simple-tap-test.yaml b/.argent/flows/simple-tap-test.yaml new file mode 100644 index 0000000000..18f61c5514 --- /dev/null +++ b/.argent/flows/simple-tap-test.yaml @@ -0,0 +1,33 @@ +steps: + - echo: Launching Expo Example + - launch: com.example.ExpoExample + - await: { visible: { text: "Empty Example" }, timeout: 15000 } + - echo: "On the Home list, search for tap example" + - type: { text: "tap", into: { id: "search-examples" } } + - echo: "Tap the example to open it" + - tap: "Basic Tap" + - echo: "Clear console" + - await: { visible: "open-console-button" } + - tap: "open-console-button" + - await: { visible: "clear-console-button" } + - tap: "clear-console-button" + - tap: "close-console-button" + - wait: 1000 # TODO: investigate why this is needed (closing modal intercepts tap on the box) + - echo: "Tap the box and check if tap count is updated" + - await: { visible: "Tap count: 0" } + - assert: { visible: "tap-box" } + - tap: "tap-box" + - assert: { visible: "Tap count: 1" } + - echo: "Check if console logs are printed in order" + - tap: "open-console-button" + - await: { visible: "close-console-button" } + - assert: { visible: "1. onBegin" } + - assert: { visible: "2. onActivate" } + - assert: { visible: "3. onDeactivate" } + - assert: { visible: "4. onFinalize" } + - tap: "close-console-button" + - wait: 1000 # TODO: investigate why this is needed (closing modal intercepts tap on the box) + - await: { visible: "Tap count: 1" } + - echo: "Tap the box again and check if tap count is updated" + - tap: "tap-box" + - assert: { visible: "Tap count: 2" } \ No newline at end of file diff --git a/.github/workflows/android-e2e.yml b/.github/workflows/android-e2e.yml new file mode 100644 index 0000000000..a988e3f473 --- /dev/null +++ b/.github/workflows/android-e2e.yml @@ -0,0 +1,132 @@ +name: Test Android e2e + +on: + pull_request: + paths: + - .argent/flows/* + - .github/workflows/android-e2e.yml + - packages/react-native-gesture-handler/android/** + - apps/expo-example/android/** + - apps/expo-example/package.json + - rnrepo.config.json + - yarn.lock + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + if: github.repository == 'software-mansion/react-native-gesture-handler' + + runs-on: ubuntu-latest + timeout-minutes: 60 + concurrency: + group: android-e2e-${{ github.ref }} + cancel-in-progress: true + + # Pin the emulator "discovery directory" for BOTH the emulator (launched by + # the action) and the simulator-server backend (spawned by the tool-server + # inside the action's script). The Android touch/key backend finds the + # running emulator's gRPC console by scanning $XDG_RUNTIME_DIR/avd/running/ + # pid_*.ini for grpc.port/grpc.token. XDG_RUNTIME_DIR is unset on GitHub + # runners and /run/user/$UID doesn't exist for the runner user, so the + # emulator and the backend can otherwise resolve DIFFERENT fallback dirs — + # the backend then logs "Failed to find any running emulator" and exits + # before becoming ready. Pinning it (checked first by both) removes that + # divergence. Job-level env reaches every step, including the emulator + # subprocess the action spawns. + env: + XDG_RUNTIME_DIR: /tmp/xdg-runtime + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Use Java 17 + uses: actions/setup-java@v4 + with: + distribution: oracle + java-version: 17 + + - name: Use Node.js 24 + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: yarn + + - name: Install node dependencies + working-directory: apps/expo-example + run: yarn install --immutable + + - name: Build app in Release mode + working-directory: apps/expo-example/android + run: ./gradlew :app:assembleRelease --console=plain -PreactNativeArchitectures=x86_64 + + - name: Install Argent + run: npx @swmansion/argent init --yes + + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + # Must exist BEFORE the emulator boots: the emulator falls back to another + # dir if XDG_RUNTIME_DIR points at a missing path. 0700 per the XDG spec. + - name: Prepare shared XDG_RUNTIME_DIR + run: | + mkdir -p "$XDG_RUNTIME_DIR" + chmod 700 "$XDG_RUNTIME_DIR" + + # Start the tool-server out-of-band and hand `flow run` its URL via + # $GITHUB_ENV. The simulator-server it later spawns inherits this step's + # env; XDG_RUNTIME_DIR (pinned at job level) keeps its emulator-discovery + # dir aligned with the emulator's, and adb is already on PATH from the + # preinstalled SDK — so it no longer has to run inside the emulator step. + # Backgrounded with our own readiness poll to dodge flow-run's hard ~15s + # in-process startup timeout. SIMSERVER_LOG=debug surfaces the backend's + # discovery/connect steps in argent-server.log (drop once green). + - name: Start Argent tool-server + run: | + nohup env SIMSERVER_LOG=debug argent server start --detach --no-auth --port 3001 \ + > argent-server.log 2>&1 & + echo "Waiting for Argent tool-server on :3001..." + for i in $(seq 1 60); do + if curl -fsS http://127.0.0.1:3001/tools >/dev/null 2>&1; then + echo "tool-server is ready" + echo "ARGENT_TOOLS_URL=http://127.0.0.1:3001" >> "$GITHUB_ENV" + exit 0 + fi + sleep 2 + done + echo "Argent tool-server failed to become ready in time" >&2 + cat argent-server.log >&2 + exit 1 + + - name: Set up emulator and run tap E2E test + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 34 + target: google_apis + arch: x86_64 + force-avd-creation: false + # `-grpc 8554` guarantees grpc.port is written to the discovery ini + # (token-less is fine — the backend handles a missing grpc.token). + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -grpc 8554 -camera-back none + disable-animations: true + # android-emulator-runner runs each line of `script` as a separate + # `sh -c`, so exported vars don't persist between lines and multi-line + # constructs (if/fi) break. Keep every command self-contained on one + # line. ARGENT_TOOLS_URL is inherited from the tool-server step above + # via $GITHUB_ENV, so `flow run` reuses that server. + script: | + adb install -r "$(find "$GITHUB_WORKSPACE/apps/expo-example/android/app/build/outputs/apk/release" -name '*.apk' | head -1)" + argent flow run simple-tap-test --device "$(adb devices | awk 'NR>1 && $2=="device"{print $1; exit}')" + + # The simulator-server's stderr (prefixed `[sim ]`) is written to the + # tool-server log — this is where a "simulator-server exited before ready" + # crash reason shows up. Always print it so failures are diagnosable. + - name: Print Argent tool-server log + if: always() + run: cat argent-server.log 2>/dev/null || echo "no argent-server.log" diff --git a/.github/workflows/ios-e2e.yml b/.github/workflows/ios-e2e.yml new file mode 100644 index 0000000000..6cc1811212 --- /dev/null +++ b/.github/workflows/ios-e2e.yml @@ -0,0 +1,122 @@ +name: Test iOS e2e + +on: + pull_request: + paths: + - .argent/flows/* + - .github/workflows/ios-e2e.yml + - packages/react-native-gesture-handler/RNGestureHandler.podspec + - packages/react-native-gesture-handler/apple/** + - apps/expo-example/ios/** + - apps/expo-example/package.json + - rnrepo.config.json + - yarn.lock + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + if: github.repository == 'software-mansion/react-native-gesture-handler' + + runs-on: macos-26 + timeout-minutes: 60 + concurrency: + group: ios-e2e-${{ github.ref }} + cancel-in-progress: true + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Use latest stable Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '26.4.1' + + - name: Use Node.js 24 + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: yarn + + - name: Install node dependencies + working-directory: apps/expo-example + run: yarn install --immutable + + - name: Build app in Release mode + working-directory: apps/expo-example/ios + run: | + xcodebuild \ + -workspace ExpoExample.xcworkspace \ + -scheme ExpoExample \ + -configuration Release \ + -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ + -derivedDataPath build \ + CODE_SIGNING_ALLOWED=NO \ + build + + - name: Set up iPhone 17 Pro simulator + run: | + UDID=$(xcrun simctl list devices available | grep -E "iPhone 17 Pro \(" | head -1 | grep -oE "[0-9A-Fa-f-]{36}" || true) + if [ -z "$UDID" ]; then + echo "iPhone 17 Pro not found, creating one" + RUNTIME=$(xcrun simctl list runtimes ios -j | jq -r '.runtimes | map(select(.isAvailable)) | last | .identifier') + UDID=$(xcrun simctl create "iPhone 17 Pro" "com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro" "$RUNTIME") + fi + echo "Using simulator $UDID" + xcrun simctl boot "$UDID" || true + xcrun simctl bootstatus "$UDID" + echo "SIMULATOR_UDID=$UDID" >> "$GITHUB_ENV" + + - name: Install app on simulator + working-directory: apps/expo-example/ios + run: | + APP_PATH=$(find build/Build/Products/Release-iphonesimulator -maxdepth 1 -name "*.app" -type d | head -1) + if [ -z "$APP_PATH" ]; then + echo "Could not find built .app" >&2 + exit 1 + fi + echo "Installing $APP_PATH on $SIMULATOR_UDID" + xcrun simctl install "$SIMULATOR_UDID" "$APP_PATH" + + - name: Install Argent + run: npx @swmansion/argent init --yes + + # Start the tool-server out-of-band and hand `flow run` its URL, so it + # reuses this server instead of spawning one in-process and hitting the + # hard ~15s startup timeout on cold CI runners (first run downloads the + # simulator-server binary). + # + # `server start --detach` can fail to return on CI (its parent keeps the + # child's stdout pipe referenced), so background it with output redirected + # to a file — never the runner's pipe — and poll the port for readiness + # ourselves. + - name: Start Argent tool-server + run: | + nohup argent server start --detach --no-auth --port 3001 \ + > argent-server.log 2>&1 & + echo "Waiting for Argent tool-server on :3001..." + for i in $(seq 1 60); do + if curl -fsS http://127.0.0.1:3001/tools >/dev/null 2>&1; then + echo "tool-server is ready" + echo "ARGENT_TOOLS_URL=http://127.0.0.1:3001" >> "$GITHUB_ENV" + exit 0 + fi + sleep 2 + done + echo "Argent tool-server failed to become ready in time" >&2 + cat argent-server.log >&2 + exit 1 + + - name: Run tap E2E test + run: argent flow run simple-tap-test --device $SIMULATOR_UDID + + # The simulator-server's stderr (prefixed `[sim ]`) is written to the + # tool-server log — this is where a "simulator-server exited before ready" + # crash reason shows up. Always print it so failures are diagnosable. + - name: Print Argent tool-server log + if: always() + run: cat argent-server.log 2>/dev/null || echo "no argent-server.log" diff --git a/apps/common-app/src/console/ConsoleModal.tsx b/apps/common-app/src/console/ConsoleModal.tsx index d7cabd28b5..546a792861 100644 --- a/apps/common-app/src/console/ConsoleModal.tsx +++ b/apps/common-app/src/console/ConsoleModal.tsx @@ -236,6 +236,7 @@ function ConsoleModalContent({ entries, visible, onClose }: ConsoleModalProps) { { + messageCounter.current += 1; + const indexedMessage = `${messageCounter.current}. ${message}`; + console.log(indexedMessage); + }; + + const logMessageWorklet = (message: string) => { + 'worklet'; + // Schedule log on the JS thread so the console interceptor can pick it up + scheduleOnRN(logMessage, message); + }; + + return logMessageWorklet; +} + export default function TapExample() { + const [count, setCount] = useState(0); const colorProgress = useSharedValue(0); + const log = useIndexedLogger(); const animatedStyle = useAnimatedStyle(() => { return { @@ -25,11 +46,20 @@ export default function TapExample() { const tapGesture = useTapGesture({ onBegin: () => { + log('onBegin'); colorProgress.value = withTiming(1, { duration: 100, }); }, + onActivate: () => { + log('onActivate'); + scheduleOnRN(setCount, count + 1); + }, + onDeactivate: () => { + log('onDeactivate'); + }, onFinalize: () => { + log('onFinalize'); colorProgress.value = withTiming(0, { duration: 100, }); @@ -38,8 +68,12 @@ export default function TapExample() { return ( + Tap count: {count} - + );