Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/scripts/android-e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
set -euo pipefail

trap 'adb logcat -d > "$GITHUB_WORKSPACE/android-logcat.txt" || true' EXIT
RECOVERY_PHRASE="${RECOVERY_PHRASE:-abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about}"

cd "$GITHUB_WORKSPACE/android"
./gradlew :app:assembleRelease -PreactNativeArchitectures=x86_64 --no-daemon
Expand All @@ -14,6 +15,7 @@ adb install -r "$APK_PATH"

cd "$GITHUB_WORKSPACE"
bash .maestro/scripts/prepare-device-motion.sh android
bash .maestro/scripts/set-system-clipboard.sh android "$RECOVERY_PHRASE"

INVITE_CODE="$(
curl --fail --silent --show-error \
Expand All @@ -25,4 +27,4 @@ INVITE_CODE_COMPACT="${INVITE_CODE//-/}"
echo "::add-mask::$INVITE_CODE"
echo "::add-mask::$INVITE_CODE_COMPACT"

maestro --platform=android test -e APP_ID=to.pubky.ring -e INVITE_CODE="$INVITE_CODE" .maestro
maestro --platform=android test -e APP_ID=to.pubky.ring -e INVITE_CODE="$INVITE_CODE" -e RECOVERY_PHRASE="$RECOVERY_PHRASE" .maestro
5 changes: 4 additions & 1 deletion .github/workflows/ios-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ jobs:
- name: Enable reduced motion
run: bash .maestro/scripts/prepare-device-motion.sh ios

- name: Set recovery phrase clipboard
run: bash .maestro/scripts/set-system-clipboard.sh ios "${RECOVERY_PHRASE:-abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about}"

- name: Build iOS simulator app
run: |
cd ios
Expand Down Expand Up @@ -156,7 +159,7 @@ jobs:

- name: Run Maestro flows
timeout-minutes: 30
run: maestro --platform=ios --device "$SIM_UDID" test -e APP_ID=app.pubkyring -e INVITE_CODE="$INVITE_CODE" .maestro
run: maestro --platform=ios --device "$SIM_UDID" test -e APP_ID=app.pubkyring -e INVITE_CODE="$INVITE_CODE" -e RECOVERY_PHRASE="${RECOVERY_PHRASE:-abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about}" .maestro

- name: Upload Maestro results
uses: actions/upload-artifact@v7
Expand Down
2 changes: 2 additions & 0 deletions .maestro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ HOMESERVER_ADMIN_PASSWORD=... yarn e2e:android

The local `yarn e2e:*` scripts prepare the target simulator/emulator for reduced motion before Maestro runs. The app respects that platform accessibility setting, so sheet and navigation animations are disabled without requiring a special e2e build.

The local and CI e2e setup also seeds the iOS native system clipboard with a valid recovery phrase for import flows. Override the iOS clipboard value with `RECOVERY_PHRASE` if a flow needs a different mnemonic. Android currently fills the recovery phrase fields word-by-word because the emulator shell does not expose a portable system clipboard setter.

## Continuous Integration

`.github/workflows/ios-e2e.yml` builds the iOS simulator app, installs Maestro, boots an iPhone 17 simulator, installs the app, and runs all flows in `.maestro`.
Expand Down
116 changes: 80 additions & 36 deletions .maestro/flows/import-pubky-mnemonic.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
appId: ${APP_ID}
name: import-pubky-mnemonic
env:
RECOVERY_PHRASE: abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
---
- launchApp:
clearState: true
Expand All @@ -19,43 +21,85 @@ name: import-pubky-mnemonic
- waitForAnimationToEnd
- assertVisible:
id: MnemonicWordInput-1
- runFlow:
when:
platform: iOS
commands:
- tapOn:
id: MnemonicWordInput-1
- inputText: abandon
- tapOn:
id: MnemonicWordInput-2
- inputText: ab
- tapOn: ability
- tapOn:
id: MnemonicWordInput-1
- runFlow:
file: ../shared/clear-input-text.yaml
env:
INPUT_ID: MnemonicWordInput-1
- runFlow:
file: ../shared/paste-system-clipboard.yaml
env:
INPUT_ID: MnemonicWordInput-1
- runFlow:
when:
platform: Android
commands:
- tapOn:
id: MnemonicWordInput-1
- inputText: ab
- tapOn: abandon
- tapOn:
id: MnemonicWordInput-2
- inputText: ab
- tapOn: abandon
- tapOn:
id: MnemonicWordInput-3
- inputText: ab
- tapOn: abandon
- tapOn:
id: MnemonicWordInput-4
- inputText: ab
- tapOn: abandon
- tapOn:
id: MnemonicWordInput-5
- inputText: ab
- tapOn: abandon
- tapOn:
id: MnemonicWordInput-6
- inputText: ab
- tapOn: abandon
- tapOn:
id: MnemonicWordInput-7
- inputText: ab
- tapOn: abandon
- tapOn:
id: MnemonicWordInput-8
- inputText: ab
- tapOn: abandon
- tapOn:
id: MnemonicWordInput-9
- inputText: ab
- tapOn: abandon
- tapOn:
id: MnemonicWordInput-10
- inputText: ab
- tapOn: abandon
- tapOn:
id: MnemonicWordInput-11
- inputText: ab
- tapOn: abandon
- tapOn:
id: MnemonicWordInput-12
- inputText: abo
- tapOn: about
- extendedWaitUntil:
visible:
id: MnemonicImportButton
timeout: 5000
- tapOn:
id: MnemonicWordInput-1
- inputText: abandon
- tapOn:
id: MnemonicWordInput-2
- inputText: abandon
- tapOn:
id: MnemonicWordInput-3
- inputText: abandon
- tapOn:
id: MnemonicWordInput-4
- inputText: abandon
- tapOn:
id: MnemonicWordInput-5
- inputText: abandon
- tapOn:
id: MnemonicWordInput-6
- inputText: abandon
- tapOn:
id: MnemonicWordInput-7
- inputText: abandon
- tapOn:
id: MnemonicWordInput-8
- inputText: abandon
- tapOn:
id: MnemonicWordInput-9
- inputText: abandon
- tapOn:
id: MnemonicWordInput-10
- inputText: abandon
- tapOn:
id: MnemonicWordInput-11
- inputText: abandon
- tapOn:
id: MnemonicWordInput-12
- inputText: about
- pressKey: Enter
id: MnemonicImportButton
- extendedWaitUntil:
visible:
id: import-success-title
Expand Down
3 changes: 3 additions & 0 deletions .maestro/scripts/run-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ set -euo pipefail

platform="${1:-}"
flow="${2:-.maestro}"
recovery_phrase="${RECOVERY_PHRASE:-abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about}"

case "$platform" in
android)
Expand Down Expand Up @@ -32,10 +33,12 @@ else
fi

bash .maestro/scripts/prepare-device-motion.sh "$platform"
bash .maestro/scripts/set-system-clipboard.sh "$platform" "$recovery_phrase"

MAESTRO_CLI_NO_ANALYTICS=true \
MAESTRO_CLI_ANALYSIS_NOTIFICATION_DISABLED=true \
maestro --platform="$platform" test \
-e APP_ID="$app_id" \
-e HOMESERVER_ADMIN_PASSWORD="${HOMESERVER_ADMIN_PASSWORD:-}" \
-e RECOVERY_PHRASE="$recovery_phrase" \
"$flow_target"
25 changes: 25 additions & 0 deletions .maestro/scripts/set-system-clipboard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail

platform="${1:-}"
text="${2:-${RECOVERY_PHRASE:-}}"

if [ -z "$text" ]; then
echo "Usage: $0 ios|android <text>" >&2
echo "Or set RECOVERY_PHRASE." >&2
exit 1
fi

case "$platform" in
ios)
device="${SIM_UDID:-${IOS_SIM_UDID:-booted}}"
printf '%s' "$text" | xcrun simctl pbcopy "$device"
;;
android)
echo "Android system clipboard is not set from adb; flows use inputText fallback." >&2
;;
*)
echo "Usage: $0 ios|android [text]" >&2
exit 1
;;
esac
7 changes: 7 additions & 0 deletions .maestro/shared/paste-system-clipboard.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
appId: ${APP_ID}
---
- tapOn:
id: ${INPUT_ID}
- longPressOn:
id: ${INPUT_ID}
- tapOn: Paste
56 changes: 56 additions & 0 deletions src/components/KeyboardAccessory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { memo, ReactElement, ReactNode } from 'react';
import { InputAccessoryView, Platform, StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

interface KeyboardAccessoryProps {
accessoryIds: string[];
children: ReactNode | (() => ReactNode);
visible: boolean;
containerStyle?: StyleProp<ViewStyle>;
androidContainerStyle?: StyleProp<ViewStyle>;
}

const KeyboardAccessory = ({
accessoryIds,
children,
visible,
containerStyle,
androidContainerStyle,
}: KeyboardAccessoryProps): ReactElement | null => {
const insets = useSafeAreaInsets();
const renderContent = (): ReactNode => (typeof children === 'function' ? children() : children);

if (Platform.OS === 'ios') {
return (
<>
{accessoryIds.map(accessoryId => (
<InputAccessoryView key={accessoryId} nativeID={accessoryId}>
<View style={visible && containerStyle}>{visible ? renderContent() : null}</View>
</InputAccessoryView>
))}
</>
);
}

if (!visible) {
return null;
}

return (
<View style={[styles.androidAccessory, { bottom: insets.bottom }, containerStyle, androidContainerStyle]}>
{renderContent()}
</View>
);
};

const styles = StyleSheet.create({
androidAccessory: {
position: 'absolute',
left: 0,
right: 0,
zIndex: 2,
elevation: 2,
},
});

export default memo(KeyboardAccessory);
Loading
Loading