diff --git a/.github/workflows/remote-push-e2e.yml b/.github/workflows/remote-push-e2e.yml new file mode 100644 index 00000000..b6fbe949 --- /dev/null +++ b/.github/workflows/remote-push-e2e.yml @@ -0,0 +1,95 @@ +name: React Native remote push E2E + +on: + pull_request: + types: [labeled] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: react-native-remote-push-e2e-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + remote-push: + name: APNs delivery and opened attribution + if: >- + github.event_name == 'workflow_dispatch' || + (github.event.action == 'labeled' && + github.event.label.name == 'run-remote-push-e2e' && + github.event.pull_request.head.repo.full_name == github.repository && + github.actor != 'dependabot[bot]') + runs-on: xcode-27 + timeout-minutes: 60 + + steps: + - name: Check out React Native SDK + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Verify Xcode 27 toolchain + uses: customerio/mobile-ci-tools/github-actions/ios/report-toolchain/v1@main + with: + expected-xcode-major: '27' + expected-ios-sdk-major: '27' + + - name: Set up Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '24' + cache: npm + + - name: Set up Ruby + uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0 + with: + ruby-version: '3.4' + bundler-cache: true + working-directory: example + + - name: Configure APNs sample + shell: bash + env: + RN_CDP_API_KEY: ${{ secrets.CUSTOMERIO_APN_WORKSPACE_CDP_API_KEY }} + RN_SITE_ID: ${{ secrets.CUSTOMERIO_APN_WORKSPACE_SITE_ID }} + run: | + set -euo pipefail + cp -R example/ios/SampleApp.xcodeproj.tracked example/ios/SampleApp.xcodeproj + cp example/src/env.ts.sample example/src/env.ts + ruby -e ' + path = "example/src/env.ts" + text = File.read(path) + .gsub("", ENV.fetch("RN_CDP_API_KEY")) + .gsub("", ENV.fetch("RN_SITE_ID")) + File.write(path, text) + ' + + - name: Install pinned Maestro + shell: bash + run: | + set -euo pipefail + maestro_zip="$RUNNER_TEMP/maestro-2.8.0.zip" + maestro_dir="$RUNNER_TEMP/maestro-2.8.0" + curl -fsSL \ + https://github.com/mobile-dev-inc/Maestro/releases/download/cli-2.8.0/maestro.zip \ + -o "$maestro_zip" + echo 'b3e561161904fb391875ca5834d5b22cf0b01c052dd1b408ad83e30d8f8951b3 '"$maestro_zip" \ + | shasum -a 256 -c - + unzip -q "$maestro_zip" -d "$maestro_dir" + echo "$maestro_dir/maestro/bin" >> "$GITHUB_PATH" + + - name: Run remote push E2E + shell: bash + env: + CIO_E2E_ARTIFACT_DIR: ${{ runner.temp }}/react-native-remote-push-e2e + MAESTRO_APP_API_KEY: ${{ secrets.MOBILE_E2E_RN_APP_API_KEY }} + run: .maestro/run_remote_push.sh + + - name: Upload sanitized evidence + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: react-native-remote-push-e2e + path: ${{ runner.temp }}/react-native-remote-push-e2e + if-no-files-found: warn + retention-days: 14 diff --git a/.gitignore b/.gitignore index 56587e6a..6c5b69f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ eslint_report.json +.maestro/.env # Config files env.js @@ -95,4 +96,4 @@ api-extractor-output/temp/ ## iOS Example app ## This file will be auto linked based ## on the target app -ios/Google-Service-Info.plist \ No newline at end of file +ios/Google-Service-Info.plist diff --git a/.maestro/.env.example b/.maestro/.env.example new file mode 100644 index 00000000..2a7486ab --- /dev/null +++ b/.maestro/.env.example @@ -0,0 +1 @@ +MAESTRO_APP_API_KEY=paste-app-api-key-here diff --git a/.maestro/README.md b/.maestro/README.md index 95e9f0cb..51151e36 100644 --- a/.maestro/README.md +++ b/.maestro/README.md @@ -14,10 +14,36 @@ JavaScript listener and `CustomerIO.initialize` are ready. The checked-in payload uses `simctl push` so the destination is deterministic and does not require changing a shared Customer.io campaign. It validates the client routing path, not backend `sent`, `delivered`, or `opened` metrics. Those -metrics remain a separate remote E2E lane using a real workspace and APNs. +metrics are covered separately by `run_remote_push.sh`, which uses the existing +APN sample, its notification service extension, a real workspace, and APNs. Run with Maestro 2.8.0 and a booted iPhone simulator: ```bash .maestro/run_scene_push.sh ``` + +Run the remote APNs loop after configuring the APN sample's ignored +`example/src/env.ts` file. The runner gives the notification service extension +the same APN workspace key for the build and restores its ignored config when +the run ends. Put the `Mobile SDK Maestro E2E` App API key from the same +`Mobile: React Native` workspace in `.maestro/.env` as shown by +`.maestro/.env.example`. Simulator APNs tokens use the sandbox environment, so +temporarily enable **Send all push notifications to sandbox** for the workspace +and restore its previous setting after the run. Use Maestro 2.8.0, then run: + +```bash +.maestro/run_remote_push.sh +``` + +The remote flow identifies a fresh customer in the `Mobile: React Native` +workspace, sends the `maestro_rn_apns_e2e` event used by its dedicated Maestro +automation (campaign 68), taps the real system notification, and requires +matching backend `delivered` and `opened` metrics. It is intentionally local or +trusted-CI only; pull requests do not receive the required workspace credential. + +The activation gate requires an iPhone 17 Pro simulator. Notification Center +is not exposed reliably through iOS accessibility, so the flow selects the +notification and its system Open action by coordinate. The runner accepts that +interaction only when the app reopens and Customer.io records `opened` for the +exact delivered message. diff --git a/.maestro/configure_nse_env.rb b/.maestro/configure_nse_env.rb new file mode 100644 index 00000000..d36b760d --- /dev/null +++ b/.maestro/configure_nse_env.rb @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby + +app_env_path, template_path, output_path = ARGV +abort 'usage: configure_nse_env.rb APP_ENV TEMPLATE OUTPUT' unless output_path + +app_env = File.read(app_env_path) +primary_config = app_env.match(/primary:\s*\{(?.*?)\}\s+satisfies/m) +abort 'APN sample primary config was not found' unless primary_config + +api_key = primary_config[:config].match(/API_KEY:\s*'(?[^']+)'/)&.[](:value) +abort 'APN sample CDP API key was not found' if api_key.nil? || api_key.empty? || api_key.include?('<') + +template = File.read(template_path) +abort 'notification service extension template placeholder was not found' unless template.include?('') + +File.write(output_path, template.sub('', api_key)) diff --git a/.maestro/remote_push.yaml b/.maestro/remote_push.yaml new file mode 100644 index 00000000..3b1957f3 --- /dev/null +++ b/.maestro/remote_push.yaml @@ -0,0 +1,197 @@ +appId: ${APP_ID} +name: React Native APNs delivery and opened attribution +--- +- runScript: + file: ../scripts/setup_run.js + +- launchApp: + clearState: false + +- extendedWaitUntil: + visible: + id: "login_button" + timeout: 15000 +- retry: + maxRetries: 2 + commands: + - runFlow: + when: + visible: "Get 20% off your first order" + commands: + - tapOn: "✕" + - tapOn: + id: "first_name_input" + - eraseText + - inputText: "Maestro Campaign Tester" + - tapOn: + id: "email_input" + - eraseText + - inputText: ${output.email} + - copyTextFrom: + id: "email_input" + - assertTrue: ${maestro.copiedText === output.email} +- hideKeyboard +- runFlow: + when: + visible: "Get 20% off your first order" + commands: + - tapOn: "✕" +- tapOn: + id: "login_button" + +- runFlow: + when: + visible: "Get 20% off your first order" + commands: + - tapOn: "✕" + +- retry: + maxRetries: 2 + commands: + - runFlow: + when: + visible: "Get 20% off your first order" + commands: + - tapOn: "✕" + - scrollUntilVisible: + element: + id: "push_permission_button" + direction: DOWN + timeout: 10000 + - runFlow: + when: + visible: "Get 20% off your first order" + commands: + - tapOn: "✕" + - tapOn: + id: "push_permission_button" +- tapOn: + text: "^Allow$" + optional: true + +- runScript: + file: ../scripts/assert_customer_activity.js + env: + RUN_EMAIL: ${output.email} + EXPECTED_ACTIVITY_TYPE: "device_change" + EXPECTED_PROPERTY_NAME: "devices.0.platform" + EXPECTED_PROPERTY_VALUE: "ios" + MIN_TIMESTAMP_SECONDS: ${output.started_at_seconds} + MAX_WAIT_MS: "45000" +- assertTrue: ${output.assert_ok === "true"} + +- retry: + maxRetries: 1 + commands: + - runFlow: + when: + visible: + id: "BackButton" + commands: + - tapOn: + id: "BackButton" + - scrollUntilVisible: + element: + id: "custom_event_button" + direction: UP + timeout: 15000 + - tapOn: + id: "custom_event_button" +- extendedWaitUntil: + visible: + id: "event_name_input" + timeout: 10000 +- runFlow: + when: + visible: "Get 20% off your first order" + commands: + - tapOn: "✕" +- tapOn: + id: "event_name_input" +- inputText: "maestro_rn_apns_e2e" +- tapOn: + id: "property_name_input" +- inputText: "run_id" +- tapOn: + id: "property_value_input" +- inputText: ${output.run_id} +- hideKeyboard +- scrollUntilVisible: + element: + id: "send_event_button" + direction: DOWN + timeout: 10000 +- runFlow: + when: + visible: "Get 20% off your first order" + commands: + - tapOn: "✕" +- tapOn: + id: "send_event_button" + +- runScript: + file: ../scripts/assert_customer_activity.js + env: + RUN_EMAIL: ${output.email} + EXPECTED_ACTIVITY_TYPE: "event" + EXPECTED_ACTIVITY_NAME: "maestro_rn_apns_e2e" + EXPECTED_PROPERTY_NAME: "run_id" + EXPECTED_PROPERTY_VALUE: ${output.run_id} + MIN_TIMESTAMP_SECONDS: ${output.started_at_seconds} + MAX_WAIT_MS: "45000" +- assertTrue: ${output.assert_ok === "true"} +- pressKey: Home + +- runScript: + file: ../scripts/assert_message_delivered.js + env: + RUN_EMAIL: ${output.email} + EXPECTED_TYPE: "push" + MIN_METRIC: "delivered" + CAMPAIGN_ID: "68" + MAX_WAIT_MS: "60000" +- assertTrue: ${output.assert_ok === "true"} +- assertTrue: ${output.message_id.length > 0} + +- runScript: + file: ../scripts/wait.js + env: + WAIT_MS: "5000" +- waitForAnimationToEnd: + timeout: 3000 +- swipe: + start: 20%, 1% + end: 20%, 90% + duration: 900 +- waitForAnimationToEnd: + timeout: 2000 +- takeScreenshot: ${ARTIFACT_DIR}/remote_push_notification_center +# Notification Center is not exposed reliably through iOS accessibility. +# Select the notification and its system Open action using the proven iPhone 17 +# Pro coordinates. The exact message-id assertion below prevents a false pass. +- tapOn: + point: "50%,82%" +- waitForAnimationToEnd: + timeout: 4000 +- runFlow: + when: + notVisible: + id: "send_event_button" + commands: + - tapOn: + point: "15%,82%" +- extendedWaitUntil: + visible: + id: "send_event_button" + timeout: 15000 + +- runScript: + file: ../scripts/assert_message_delivered.js + env: + RUN_EMAIL: ${output.email} + EXPECTED_TYPE: "push" + MIN_METRIC: "opened" + CAMPAIGN_ID: "68" + EXPECTED_MESSAGE_ID: ${output.message_id} + MAX_WAIT_MS: "60000" +- assertTrue: ${output.assert_ok === "true"} diff --git a/.maestro/run_remote_push.sh b/.maestro/run_remote_push.sh new file mode 100755 index 00000000..031eac09 --- /dev/null +++ b/.maestro/run_remote_push.sh @@ -0,0 +1,232 @@ +#!/usr/bin/env bash + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +MAESTRO_DIR="$REPO_ROOT/.maestro" +SAMPLE_ROOT="$REPO_ROOT/example" +APP_ID="io.customer.ami" +MOBILE_E2E_REF="7c7912eedc96fdd623dcb8a7c0d9111feae56d39" +DEVELOPER_DIR="${DEVELOPER_DIR:-$(xcode-select -p)}" +if [[ "$DEVELOPER_DIR" == */CommandLineTools && -d /Applications/Xcode.app/Contents/Developer ]]; then + DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer +fi +export DEVELOPER_DIR +export PUSH_PROVIDER=apn +export MAESTRO_CLI_NO_ANALYTICS=1 + +if ! bundle _2.6.9_ --version >/dev/null 2>&1; then + if [[ "${CIO_E2E_RUBY_READY:-false}" != "true" ]] && command -v mise >/dev/null 2>&1; then + exec env CIO_E2E_RUBY_READY=true mise x ruby@3.4 -- "$0" "$@" + fi + echo "error: Ruby 3.4 with Bundler 2.6.9 is required" >&2 + exit 2 +fi + +if [[ -f "$MAESTRO_DIR/.env" ]]; then + set -a + # shellcheck disable=SC1091 + source "$MAESTRO_DIR/.env" + set +a +fi +: "${MAESTRO_APP_API_KEY:=${MAESTRO_EXT_API_KEY:-}}" +: "${MAESTRO_EXT_API_BASE_URL:=https://api.customer.io/v1}" +export MAESTRO_APP_API_KEY MAESTRO_EXT_API_BASE_URL + +die() { + echo "error: $*" >&2 + exit 2 +} + +for required_command in bundle curl git jq maestro node npm plutil python3 ruby xcodebuild xcrun; do + command -v "$required_command" >/dev/null 2>&1 || die "required command '$required_command' is not installed" +done + +maestro_version="$(MAESTRO_CLI_NO_ANALYTICS=1 maestro --version | tr -d '\r')" +[[ "$maestro_version" == "2.8.0" ]] || die "Maestro 2.8.0 is required; found '$maestro_version'" + +[[ -n "${MAESTRO_APP_API_KEY:-}" ]] || die "MAESTRO_APP_API_KEY is missing; set it or create .maestro/.env" +[[ "$MAESTRO_APP_API_KEY" != *paste-* ]] || die "MAESTRO_APP_API_KEY still contains a placeholder" + +app_env="$SAMPLE_ROOT/src/env.ts" +nse_env="$SAMPLE_ROOT/ios/NotificationServiceExtension/Env.swift" +[[ -f "$app_env" ]] || die "APN sample config is missing: $app_env" + +backend_status="$(curl -sS --retry 3 --retry-all-errors --retry-delay 2 --connect-timeout 10 \ + -o /dev/null -w '%{http_code}' \ + -H "Authorization: Bearer $MAESTRO_APP_API_KEY" \ + "$MAESTRO_EXT_API_BASE_URL/customers?email=maestro-doctor-no-match%40cio.test" || true)" +[[ "$backend_status" == "200" ]] || die "Customer.io App API preflight returned HTTP ${backend_status:-unreachable}" + +campaign_response="$(curl -sS --retry 3 --retry-all-errors --retry-delay 2 --connect-timeout 10 \ + -w $'\n%{http_code}' \ + -H "Authorization: Bearer $MAESTRO_APP_API_KEY" \ + "$MAESTRO_EXT_API_BASE_URL/campaigns/68" || true)" +campaign_status="${campaign_response##*$'\n'}" +campaign_body="${campaign_response%$'\n'*}" +[[ "$campaign_status" == "200" ]] || \ + die "App API key cannot access campaign 68 in the Mobile: React Native workspace (HTTP ${campaign_status:-unreachable})" +campaign_name="$(printf '%s' "$campaign_body" | jq -r '.campaign.name // .name // empty' 2>/dev/null || true)" +[[ "$campaign_name" == "Maestro React Native APNs E2E" ]] || \ + die "campaign 68 is not the expected Mobile: React Native Maestro APNs automation" + +device_id="${E2E_DEVICE_ID:-}" +simulator_name="${E2E_SIMULATOR_NAME:-}" +if [[ -z "$device_id" && -n "$simulator_name" ]]; then + device_id="$(xcrun simctl list devices available -j | jq -r --arg name "$simulator_name" \ + '[.devices[][] | select(.name == $name)][0].udid // empty')" +fi +if [[ -z "$device_id" && -z "$simulator_name" ]]; then + device_id="$(xcrun simctl list devices booted -j | jq -r \ + '[.devices[][] | select(.state == "Booted") | select(.name | startswith("iPhone"))][0].udid // empty')" +fi +if [[ -z "$device_id" ]]; then + simulator_name="${simulator_name:-iPhone 17 Pro}" + device_id="$(xcrun simctl list devices available -j | jq -r --arg name "$simulator_name" \ + '[.devices[][] | select(.name == $name)][0].udid // empty')" +fi +[[ -n "$device_id" ]] || die "no available iPhone simulator; set E2E_DEVICE_ID or E2E_SIMULATOR_NAME" +device_type="$(xcrun simctl list devices available -j | jq -r --arg id "$device_id" \ + '[.devices[][] | select(.udid == $id)][0].deviceTypeIdentifier // empty')" +[[ "$device_type" == "com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro" ]] || \ + die "remote push activation requires an iPhone 17 Pro simulator; selected device type is '${device_type:-unknown}'" +simulator_started_by_runner=false +if ! xcrun simctl list devices booted -j | jq -e --arg id "$device_id" \ + 'any(.devices[][]; .udid == $id and .state == "Booted")' >/dev/null; then + xcrun simctl boot "$device_id" + simulator_started_by_runner=true +fi +xcrun simctl bootstatus "$device_id" -b + +temp_base="${TMPDIR:-/tmp}" +temp_base="${temp_base%/}" +run_root="$(mktemp -d "$temp_base/cio-rn-remote-push.XXXXXX")" +harness="$run_root/mobile-e2e" +derived_data="$run_root/derived-data" +artifacts="$run_root/maestro-artifacts" +artifact_export_dir="${CIO_E2E_ARTIFACT_DIR:-}" +installed_app=false +nse_env_generated=false +nse_env_backup="$run_root/Env.swift.original" +package_lock="$SAMPLE_ROOT/package-lock.json" +package_lock_backup="$run_root/example-package-lock.json" +gemfile_lock="$SAMPLE_ROOT/Gemfile.lock" +gemfile_lock_backup="$run_root/example-Gemfile.lock" +cp "$package_lock" "$package_lock_backup" +cp "$gemfile_lock" "$gemfile_lock_backup" +# shellcheck disable=SC2329 +cleanup() { + set +e + if [[ -d "$harness" && -d "$artifacts" ]]; then + python3 "$harness/scripts/redact_artifacts.py" "$artifacts" >/dev/null 2>&1 || true + fi + if [[ "$installed_app" == true && "${CIO_E2E_KEEP_APP:-false}" != "true" ]]; then + xcrun simctl terminate "$device_id" "$APP_ID" >/dev/null 2>&1 || true + xcrun simctl uninstall "$device_id" "$APP_ID" >/dev/null 2>&1 || true + fi + if [[ "$simulator_started_by_runner" == true ]]; then + xcrun simctl shutdown "$device_id" >/dev/null 2>&1 || true + fi + if [[ "$nse_env_generated" == true ]]; then + if [[ -f "$nse_env_backup" ]]; then + cp "$nse_env_backup" "$nse_env" + else + rm -f -- "$nse_env" + fi + fi + cp "$package_lock_backup" "$package_lock" + cp "$gemfile_lock_backup" "$gemfile_lock" + case "$run_root" in + "$temp_base"/cio-rn-remote-push.*) rm -rf -- "$run_root" ;; + esac +} +trap cleanup EXIT + +ruby "$MAESTRO_DIR/configure_nse_env.rb" \ + "$app_env" \ + "$SAMPLE_ROOT/ios/NotificationServiceExtension/Env.swift.sample" \ + "$run_root/Env.swift.generated" +if [[ -f "$nse_env" ]]; then + cp "$nse_env" "$nse_env_backup" +fi +nse_env_generated=true +cp "$run_root/Env.swift.generated" "$nse_env" + +IOS_CDP_API_KEY="$(ruby -e 'print File.read(ARGV[0])[/CDP_API_KEY\s*=\s*"([^"]+)"/, 1].to_s' "$run_root/Env.swift.generated")" +IOS_SITE_ID="$(ruby -e ' + text = File.read(ARGV[0]) + primary = text[/primary:\s*\{(.*?)\}\s+satisfies/m, 1].to_s + print primary[/SITE_ID:\s*'"'"'([^'"'"']+)'"'"'/, 1].to_s +' "$app_env")" +[[ -n "$IOS_CDP_API_KEY" && -n "$IOS_SITE_ID" ]] || die "APN sample redaction values could not be resolved" +[[ "$IOS_SITE_ID" != *'<'* ]] || die "APN sample SITE_ID still contains a placeholder" +export IOS_CDP_API_KEY IOS_SITE_ID + +git init -q "$harness" +git -C "$harness" remote add origin https://github.com/customerio/mobile-e2e.git +git -C "$harness" fetch -q --depth 1 origin "$MOBILE_E2E_REF" +git -C "$harness" checkout -q --detach FETCH_HEAD +[[ "$(git -C "$harness" rev-parse HEAD)" == "$MOBILE_E2E_REF" ]] || die "mobile-e2e harness checkout does not match the reviewed commit" +cp "$MAESTRO_DIR/remote_push.yaml" "$harness/flows/reactnative_remote_push.yaml" + +cd "$REPO_ROOT" +npm ci +cd "$SAMPLE_ROOT" +bundle install +npm run ci:install_ios + +xcodebuild -quiet \ + -workspace ios/SampleApp.xcworkspace \ + -scheme SampleApp \ + -configuration Release \ + -destination "platform=iOS Simulator,id=$device_id" \ + -derivedDataPath "$derived_data" \ + CODE_SIGN_IDENTITY=- \ + CODE_SIGNING_ALLOWED=YES \ + CODE_SIGNING_REQUIRED=YES \ + build >"$run_root/xcodebuild.log" 2>&1 || { + if ruby -e ' + key = ENV.fetch("IOS_CDP_API_KEY") + site_id = ENV.fetch("IOS_SITE_ID") + path = ARGV[0] + redacted = File.read(path).gsub(key, "[REDACTED]").gsub(site_id, "[REDACTED]") + File.write(path, redacted) + ' "$run_root/xcodebuild.log"; then + tail -100 "$run_root/xcodebuild.log" >&2 + else + echo "React Native APN sample build failed; log withheld because credential redaction failed" >&2 + fi + die "React Native APN sample build failed" + } +echo "React Native APN sample build succeeded" + +app_path="$derived_data/Build/Products/Release-iphonesimulator/SampleApp.app" +[[ -d "$app_path" ]] || die "built sample app was not found at $app_path" +simulator_entitlements="$derived_data/Build/Intermediates.noindex/SampleApp.build/Release-iphonesimulator/SampleApp.build/SampleApp.app-Simulated.xcent" +[[ -f "$simulator_entitlements" ]] || die "built sample app simulator entitlements were not found" +[[ "$(plutil -extract aps-environment raw "$simulator_entitlements")" == "development" ]] || \ + die "built sample app is missing the development APNs simulator entitlement" +xcrun simctl uninstall "$device_id" "$APP_ID" >/dev/null 2>&1 || true +xcrun simctl install "$device_id" "$app_path" +installed_app=true + +mkdir -p "$artifacts" +set +e +maestro --device "$device_id" test \ + --debug-output "$artifacts" \ + --flatten-debug-output \ + -e "APP_ID=$APP_ID" \ + -e "ARTIFACT_DIR=$artifacts" \ + -e "MAESTRO_APP_API_KEY=$MAESTRO_APP_API_KEY" \ + -e "MAESTRO_EXT_API_BASE_URL=$MAESTRO_EXT_API_BASE_URL" \ + "$harness/flows/reactnative_remote_push.yaml" +result=$? +set -e + +python3 "$harness/scripts/redact_artifacts.py" "$artifacts" +python3 "$harness/scripts/redact_artifacts.py" "$artifacts" --check +if [[ -n "$artifact_export_dir" ]]; then + mkdir -p "$artifact_export_dir" + cp -R "$artifacts/." "$artifact_export_dir/" +fi +exit "$result" diff --git a/example/src/screens/home.tsx b/example/src/screens/home.tsx index 281af2f5..e191a4dd 100644 --- a/example/src/screens/home.tsx +++ b/example/src/screens/home.tsx @@ -40,6 +40,7 @@ export const HomeScreen = ({ }} />