UNPKG

react-native-appsflyer

Version:
397 lines (388 loc) 15.5 kB
{ "_meta": { "plan_id": "reactnative-e2e", "plugin": "reactnative", "version": "1.1.0", "description": "Pre-publish E2E test plan for the AppsFlyer React Native plugin. Runs against plugin source in example/. Covers six scenarios: cold launch, background deep link, foreground deep link, custom events, identity APIs, and consent/stop. Mapped to E2E-001..E2E-006 in appsflyer-mobile-plugin-tooling/contracts/e2e-test-contract.md.", "platforms": ["android", "ios"], "schema_version": "1.0.0", "tooling_contract_ref": "E2E-001, E2E-002, E2E-003, E2E-004, E2E-005, E2E-006" }, "config": { "android": { "package_name": "com.appsflyer.engagement", "activity": "com.appsflyer.qa.reactnative.MainActivity", "apk_path": "example/android/app/build/outputs/apk/debug/app-debug.apk", "build_cmd": "cd example/android && ./gradlew assembleDebug" }, "ios": { "bundle_id": "com.appsflyer.qa.reactnative", "app_path": "example/ios/build/Build/Products/Debug-iphonesimulator/example.app", "build_cmd": "cd example/ios && xcodebuild build -workspace example.xcworkspace -scheme example -configuration Debug -destination 'platform=iOS Simulator,id=$IOS_SIMULATOR_UDID' -derivedDataPath build" } }, "phases": [ { "id": "phase_1", "name": "Cold launch coverage", "scenario_ref": "E2E-001", "description": "Fresh install. Validate SDK startup, pre/post-start APIs, three auto-launched events with HTTP 200, install conversion data with is_first_launch=true, and onDeepLinking NOT_FOUND on clean launch.", "requires_fresh_install": true, "wait_after_launch_sec": 420, "checks": [ { "id": "sdk_started", "description": "startSDK was called", "type": "log_contains", "pattern": "[AF_QA][startSDK] result:", "fail_action": "abort" }, { "id": "is_first_launch_true", "description": "onInstallConversionData fires with is_first_launch=true", "type": "log_contains", "pattern": "[AF_QA][CALLBACK][onInstallConversionData]", "payload_check": {"field": "is_first_launch", "expected": "true"}, "fail_action": "abort" }, { "id": "pre_start_apis_complete", "description": "Pre-start auto APIs ran", "type": "log_contains", "pattern": "[AF_QA][AUTO_APIS] --- Pre-start auto APIs complete ---", "fail_action": "fail" }, { "id": "post_start_apis_complete", "description": "Post-start auto APIs ran", "type": "log_contains", "pattern": "[AF_QA][AUTO_APIS] --- Post-start auto APIs complete ---", "fail_action": "fail" }, { "id": "get_sdk_version", "description": "getSDKVersion returns a value", "type": "log_contains", "pattern": "[AF_QA][getSDKVersion] result:", "fail_action": "fail" }, { "id": "get_appsflyer_uid", "description": "getAppsFlyerUID returns a value", "type": "log_contains", "pattern": "[AF_QA][getAppsFlyerUID] result:", "fail_action": "fail" }, { "id": "event_af_demo_launch", "description": "af_demo_launch event fires successfully", "type": "log_contains", "pattern": "[AF_QA][logEvent(af_demo_launch)] result:", "fail_action": "fail" }, { "id": "event_af_purchase", "description": "af_purchase event fires successfully", "type": "log_contains", "pattern": "[AF_QA][logEvent(af_purchase)] result:", "fail_action": "fail" }, { "id": "event_af_content_view", "description": "af_content_view event fires successfully", "type": "log_contains", "pattern": "[AF_QA][logEvent(af_content_view)] result:", "fail_action": "fail" }, { "id": "http_200_count", "description": "At least 3 HTTP 200 responses from AppsFlyer servers", "type": "count_matches", "pattern": "response code:200 OK|response_status=200", "minimum": 3, "fail_action": "fail" }, { "id": "on_deep_linking_callback", "description": "onDeepLinking fires (NOT_FOUND expected on clean launch)", "type": "log_contains", "pattern": "[AF_QA][CALLBACK][onDeepLinking]", "fail_action": "fail" }, { "id": "no_fatal_errors", "description": "No fatal exceptions or SDK errors in logs", "type": "absent", "patterns": ["Fatal Exception", "FATAL", "[AF_QA][startSDK] error:", "response code:4", "response code:5"], "fail_action": "fail" } ] }, { "id": "phase_2", "name": "Background deep link", "scenario_ref": "E2E-002", "description": "App backgrounded after Phase 1, then deep link triggers re-entry. Verifies URL is delivered to native layer. onDeepLinking attribution (FOUND/value) requires real OneLink and is warn-only in CI. LAUNCH event receives HTTP 200.", "requires_fresh_install": false, "wait_after_trigger_sec": 15, "deep_link_url": "afqa-reactnative://deeplink?deep_link_value=qa_deeplink_bg&af_sub1=background_test&pid=testmedia&c=deeplink_test", "pre_actions": { "android": ["adb shell input keyevent KEYCODE_HOME", "sleep 2"], "ios": ["xcrun simctl terminate {{UDID}} {{BUNDLE_ID}}", "sleep 1"] }, "trigger": { "android": "adb shell am start -W -a android.intent.action.VIEW -d \"{{DEEP_LINK_URL}}\"", "ios": "xcrun simctl launch {{UDID}} {{BUNDLE_ID}} -deepLinkURL \"{{DEEP_LINK_URL}}\"" }, "checks": [ { "id": "deeplink_url_received", "description": "Deep link URL delivered to native app layer", "type": "log_contains", "pattern": "[AF_QA][DEEPLINK_NATIVE]", "fail_action": "fail" }, { "id": "deeplink_callback_fired", "description": "onDeepLinking callback fires after deep link (any status)", "type": "log_contains", "pattern": "[AF_QA][CALLBACK][onDeepLinking]", "fail_action": "fail" }, { "id": "deeplink_status_found", "description": "onDeepLinking fires with Status.FOUND (requires real OneLink attribution — expected to warn in CI)", "type": "log_contains", "pattern": "status=FOUND", "fail_action": "warn" }, { "id": "deeplink_value_bg", "description": "deepLinkValue matches qa_deeplink_bg (requires server-side attribution — expected to warn in CI)", "type": "log_contains", "pattern": "deepLinkValue=qa_deeplink_bg", "fail_action": "warn" }, { "id": "launch_http_200", "description": "LAUNCH event receives HTTP 200 after deep link re-entry", "type": "count_matches", "pattern": "response code:200 OK|response_status=200", "minimum": 1, "fail_action": "fail" }, { "id": "no_fatal_errors", "description": "No fatal exceptions after deep link", "type": "absent", "patterns": ["Fatal Exception", "FATAL"], "fail_action": "fail" } ] }, { "id": "phase_3", "name": "Foreground deep link", "scenario_ref": "E2E-003", "description": "Fresh install. App in foreground after SDK start. Brief launcher switch, then deep link. Verifies URL delivery and callback firing. Attribution checks (FOUND/value) are warn-only in CI. SDK start and conversion data are validated in Phase 1.", "requires_fresh_install": true, "wait_after_launch_sec": 420, "wait_after_trigger_sec": 15, "deep_link_url": "afqa-reactnative://deeplink?deep_link_value=qa_deeplink_fg&af_sub1=foreground_test&pid=testmedia&c=deeplink_test", "pre_actions": { "android": ["adb shell am start -a android.intent.action.MAIN -c android.intent.category.HOME", "sleep 1"], "ios": ["xcrun simctl terminate {{UDID}} {{BUNDLE_ID}}", "sleep 1"] }, "trigger": { "android": "adb shell am start -W -a android.intent.action.VIEW -d \"{{DEEP_LINK_URL}}\"", "ios": "xcrun simctl launch {{UDID}} {{BUNDLE_ID}} -deepLinkURL \"{{DEEP_LINK_URL}}\"" }, "checks": [ { "id": "deeplink_url_received", "description": "Deep link URL delivered to native app layer", "type": "log_contains", "pattern": "[AF_QA][DEEPLINK_NATIVE]", "fail_action": "fail" }, { "id": "deeplink_callback_fired", "description": "onDeepLinking callback fires after deep link (any status)", "type": "log_contains", "pattern": "[AF_QA][CALLBACK][onDeepLinking]", "fail_action": "fail" }, { "id": "deeplink_status_found", "description": "onDeepLinking fires with Status.FOUND (requires real OneLink attribution — expected to warn in CI)", "type": "log_contains", "pattern": "status=FOUND", "fail_action": "warn" }, { "id": "deeplink_value_fg", "description": "deepLinkValue matches qa_deeplink_fg (requires server-side attribution — expected to warn in CI)", "type": "log_contains", "pattern": "deepLinkValue=qa_deeplink_fg", "fail_action": "warn" }, { "id": "no_fatal_errors", "description": "No fatal exceptions", "type": "absent", "patterns": ["Fatal Exception", "FATAL"], "fail_action": "fail" } ] }, { "id": "phase_4", "name": "Custom in-app event with parameters", "scenario_ref": "E2E-004", "description": "Trigger af_qa_custom_purchase with rich parameters (string, number, bool, nested map). Verify all parameter keys are serialized, HTTP 200 returned, and no logEvent errors.", "requires_fresh_install": false, "wait_after_launch_sec": 420, "checks": [ { "id": "custom_event_logged", "description": "af_qa_custom_purchase logEvent fires", "type": "log_contains", "pattern": "[AF_QA][logEvent(af_qa_custom_purchase)]", "fail_action": "fail" }, { "id": "params_complete", "description": "All 5 parameter keys present in the custom event log line", "type": "log_contains", "pattern": "[AF_QA][logEvent] name=af_qa_custom_purchase params=", "fail_action": "fail" }, { "id": "http_200_event", "description": "HTTP 200 for the custom event", "type": "count_matches", "pattern": "response code:200 OK|response_status=200", "minimum": 1, "fail_action": "fail" }, { "id": "no_logEvent_error", "description": "No logEvent errors for custom purchase", "type": "absent", "patterns": ["[AF_QA][logEvent(af_qa_custom_purchase)] error:"], "fail_action": "fail" }, { "id": "no_fatal_errors", "description": "No fatal exceptions or process crashes", "type": "absent", "patterns": ["Fatal Exception", "FATAL"], "fail_action": "fail" } ] }, { "id": "phase_5", "name": "Identity APIs round-trip", "scenario_ref": "E2E-005", "description": "Fresh install. Verify setCustomerUserId, setCurrencyCode, setAdditionalData propagate correctly. Identity-check event receives HTTP 200. is_first_launch=true still fires.", "requires_fresh_install": true, "wait_after_launch_sec": 420, "checks": [ { "id": "customer_user_id_set", "description": "setCustomerUserId readback present", "type": "log_contains", "pattern": "[AF_QA][setCustomerUserId] result:", "fail_action": "fail" }, { "id": "currency_code", "description": "setCurrencyCode readback present", "type": "log_contains", "pattern": "[AF_QA][setCurrencyCode] result:", "fail_action": "fail" }, { "id": "additional_data", "description": "setAdditionalData readback present", "type": "log_contains", "pattern": "[AF_QA][setAdditionalData] result:", "fail_action": "fail" }, { "id": "http_200_identity_event", "description": "HTTP 200 for the identity-check event", "type": "count_matches", "pattern": "response code:200 OK|response_status=200", "minimum": 1, "fail_action": "fail" }, { "id": "is_first_launch_true", "description": "onInstallConversionData still fires with is_first_launch=true", "type": "log_contains", "pattern": "[AF_QA][CALLBACK][onInstallConversionData]", "payload_check": {"field": "is_first_launch", "expected": "true"}, "fail_action": "fail" }, { "id": "no_fatal_errors", "description": "No fatal exceptions or process crashes", "type": "absent", "patterns": ["Fatal Exception", "FATAL"], "fail_action": "fail" } ] }, { "id": "phase_6", "name": "Consent / SDK stop toggle", "scenario_ref": "E2E-006", "description": "stop(true) suppresses outbound events. stop(false) resumes them. Verify suppressed event does NOT get HTTP 200, resumed event DOES get HTTP 200.", "requires_fresh_install": false, "wait_after_launch_sec": 420, "checks": [ { "id": "stop_true", "description": "stop(true) readback present", "type": "log_contains", "pattern": "[AF_QA][stop] result: true", "fail_action": "fail" }, { "id": "suppressed_event_no_result", "description": "Suppressed event does not get a success result while SDK is stopped", "type": "absent", "patterns": ["[AF_QA][logEvent(af_qa_suppressed)] result:"], "fail_action": "fail" }, { "id": "stop_false", "description": "stop(false) readback present", "type": "log_contains", "pattern": "[AF_QA][stop] result: false", "fail_action": "fail" }, { "id": "resumed_event_http_200", "description": "Resumed event fires and receives HTTP 200", "type": "log_contains", "pattern": "[AF_QA][logEvent(af_qa_resumed)] result:", "fail_action": "fail" }, { "id": "no_fatal_errors", "description": "No fatal exceptions throughout stop/resume cycle", "type": "absent", "patterns": ["Fatal Exception", "FATAL"], "fail_action": "fail" } ] } ], "report": { "output_dir": ".af-e2e/reports", "format": "json" } }