Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
b5f9489
add E2E test infrastructure for Android and iOS
Kobikg78 May 17, 2026
97d3d41
add manual E2E workflow_dispatch trigger
Kobikg78 May 17, 2026
85c7fca
fix e2e-manual: add push trigger on feature branch
Kobikg78 May 17, 2026
66f0ed8
fix CI workflows: allowDirtyBuild + split iOS into Linux/macOS jobs
Kobikg78 May 17, 2026
ddb04b7
fix CI: align Unity version to 2020.3.41f1 (matches master) and fix i…
Kobikg78 May 17, 2026
48bafd7
fix CI: use Unity 6000.3.5f1 for test-app builds, fix iOS strongSelf …
Kobikg78 May 17, 2026
8b663da
fix ProjectVersion.txt: correct revision hash for Unity 6000.3.5f1
Kobikg78 May 17, 2026
5b24fa2
fix CI: Android same-file cp, iOS build for arm64 on M1 runner
Kobikg78 May 17, 2026
76c24a3
fix iOS xcodebuild: use x86_64 to match Unity Linux-generated libs
Kobikg78 May 17, 2026
50cd781
fix iOS: set CURRENT_PROJECT_VERSION for valid CFBundleVersion in Inf…
Kobikg78 May 17, 2026
aaf2013
fix Android CI: enable KVM access so emulator boots with hardware acc…
Kobikg78 May 17, 2026
792a5f0
fix CI: Android x86_64 ABI for emulator, iOS linker missing Swift ove…
Kobikg78 May 17, 2026
ce4399e
fix CI: Android adb push line continuation, iOS xcode error capture
Kobikg78 May 17, 2026
8754dfd
fix iOS CI: remove broken PurchaseConnector privacy bundle build phases
Kobikg78 May 17, 2026
67fcdcb
fix Android E2E: read .env from StreamingAssets, enable file logging
Kobikg78 May 17, 2026
ae87eeb
fix iOS CI: fully remove privacy bundle target instead of clearing ph…
Kobikg78 May 17, 2026
d2de54f
fix iOS CI: remove all _Privacy pod targets generically
Kobikg78 May 17, 2026
41e4014
fix Android E2E: root-cat fallback + no logcat tail limit; fix iOS: m…
Kobikg78 May 17, 2026
15358c5
fix Android E2E: add || true to prevent set -e exit on failed cat
Kobikg78 May 17, 2026
24ea589
fix Android E2E: 32M logcat buffer + logcat fallback in marker peek
Kobikg78 May 17, 2026
452fa98
ci(ios): switch to macos-14 arm64 simulator to avoid macos-13 queue
Kobikg78 May 17, 2026
f85e76f
fix Android E2E: correct activity name + add ARM64 build target
Kobikg78 May 17, 2026
de0c518
ci(ios): single macOS job — Unity builds on macos-14 producing arm64 …
Kobikg78 May 18, 2026
61c272b
ci(ios): iOS-only push workflow, pin Xcode 15.4 for x86_64 Swift over…
Kobikg78 May 18, 2026
d1d3c84
ci(android): Android-only workflow + post_marker_wait_sec for async c…
Kobikg78 May 18, 2026
9d02323
ci(ios): install Unity natively on macos-14 — arm64 libs match local …
Kobikg78 May 18, 2026
71fb07f
ci(android): add push trigger to e2e-android-only workflow
Kobikg78 May 18, 2026
2f7d457
ci: make iOS and Android workflows manual-dispatch only
Kobikg78 May 18, 2026
5d89fc6
ci(ios): add Unity 6000.3.5f1 changeset hash for Hub headless install
Kobikg78 May 18, 2026
7782b0e
ci(ios): pipe newline to Unity Hub to select Apple silicon non-intera…
Kobikg78 May 18, 2026
a5634d5
ci(ios): install Unity via direct pkg download — bypass Unity Hub hea…
Kobikg78 May 18, 2026
2825c64
fix(e2e): force-stop app before Android deep link phases instead of K…
Kobikg78 May 18, 2026
eeaed2b
fix(ios-ci): patch arm64 simulator libs + stub _Privacy.bundle in res…
Kobikg78 May 18, 2026
f76ec49
fix(ios-ci): use find to locate arm64 Trampoline libs — works for bot…
Kobikg78 May 18, 2026
7f95acd
fix(ios): remove PurchaseConnector from E2E Podfile; guard PC code wi…
Kobikg78 May 18, 2026
1f6869e
ci(ios): run E2E 3× sequentially for stability validation
Kobikg78 May 18, 2026
c804c69
ci(ios): switch to macos-15 + Xcode 26, restore PurchaseConnector
Kobikg78 May 18, 2026
d6fc343
test: try PurchaseConnector 6.18.1 in E2E iOS Podfile
Kobikg78 May 18, 2026
df8a32b
test: use AppsFlyerFramework 6.18.0 + PurchaseConnector 6.18.1 in E2E…
Kobikg78 May 18, 2026
ce8a11c
test: revert to macos-14/Xcode16, remove PC, restore __has_include gu…
Kobikg78 May 18, 2026
fd2f21c
revert: restore exact state of successful run 26033290223
Kobikg78 May 18, 2026
6386eb3
ci: complete RC pipeline aligned with appsflyer-mobile-plugin-tooling…
Kobikg78 May 18, 2026
699796b
ci: simplify rc-release.yml — inputs, branch prep, iOS+Android E2E only
Kobikg78 May 18, 2026
1e2618c
Merge pull request #393 from AppsFlyerSDK/plugins-effort-reduction-wo…
Kobikg78 May 18, 2026
70d2f1f
test: add UNITY_SERIAL to return license step
Kobikg78 May 18, 2026
551b511
refactor: replace game-ci Docker with direct Unity 6000.3.5f1 install…
Kobikg78 May 18, 2026
6a5f863
fix: remove non-existent Linux Android module download
Kobikg78 May 18, 2026
6961e32
test: remove unity-activate and unity-return-license, let builder han…
Kobikg78 May 18, 2026
0261ceb
fix: return Unity license via Docker after builder completes
Kobikg78 May 18, 2026
ecc9c9d
fix: bump-version.sh must also patch test-app gradle and ios-pod-install
Kobikg78 May 19, 2026
2352cc1
ci: add version bump verification step to rc-release pipeline
Kobikg78 May 19, 2026
81888d4
Merge pull request #399 from AppsFlyerSDK/plugins-effort-reduction-wo…
Kobikg78 May 19, 2026
2e3a793
ci: align workflow files with master fixes
Kobikg78 May 19, 2026
8e0e312
Merge pull request #401 from AppsFlyerSDK/plugins-effort-reduction-wo…
Kobikg78 May 19, 2026
8d47ee3
Merge master into development — keep verify step and git add fixes
Kobikg78 May 19, 2026
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
393 changes: 393 additions & 0 deletions .af-e2e/test-plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,393 @@
{
"_meta": {
"plan_id": "unity-e2e",
"plugin": "unity",
"version": "1.0.0",
"description": "End-to-end test plan for the AppsFlyer Unity plugin. Runs against plugin source (path: ../Assets/AppsFlyer) in test-app/. Covers six scenarios: cold launch, background deep link, foreground deep link, custom event with parameters, identity APIs round-trip, and consent / SDK stop toggle. 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.engagement.AppsFlyerUnityActivity",
"apk_path": "test-app/Build/Android/com.appsflyer.engagement.apk",
"build_cmd": "echo 'Build handled by game-ci/unity-builder in CI'"
},
"ios": {
"bundle_id": "com.appsflyer.engagement",
"app_path": "test-app/Build/iOS-Simulator/UnityQATest.app",
"build_cmd": "echo 'Build handled by game-ci/unity-builder + xcodebuild in CI'"
}
},

"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, and all standard callbacks.",
"requires_fresh_install": true,
"wait_after_launch_sec": 240,
"post_marker_wait_sec": 30,
"checks": [
{
"id": "sdk_started",
"description": "startSDK returns SUCCESS",
"type": "log_contains",
"pattern": "[AF_QA][startSDK] result: SUCCESS",
"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",
"type": "log_contains",
"pattern": "[AF_QA][logEvent(af_demo_launch)] result:",
"fail_action": "fail"
},
{
"id": "event_af_purchase",
"description": "af_purchase event fires",
"type": "log_contains",
"pattern": "[AF_QA][logEvent: af_purchase sent] result:",
"fail_action": "fail"
},
{
"id": "event_af_content_view",
"description": "af_content_view event fires",
"type": "log_contains",
"pattern": "[AF_QA][logEvent: af_content_view sent] 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|responseCode=200",
"minimum": 3,
"fail_action": "fail"
},
{
"id": "on_deep_linking_callback",
"description": "onDeepLinking callback 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 start 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 is brought down after Phase 1 SDK start; the deep-link URL re-launches it. On Android the app is HOME-keyed to background and re-entered via VIEW intent. On iOS the app is terminated and re-launched with -deepLinkURL, which UnityAppControllerDeepLink replays through application:openURL:options: — same SDK pipeline as Flutter's AppDelegate. Both paths fire onDeepLinking with status=FOUND.",
"requires_fresh_install": false,
"wait_after_trigger_sec": 90,
"deep_link_url": "afqa-unity://deeplink?deep_link_value=qa_deeplink_bg&af_sub1=background_test&pid=testmedia&c=deeplink_test",
"pre_actions": {
"android": ["adb shell am force-stop com.appsflyer.engagement", "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_found",
"description": "onDeepLinking fires with status=FOUND",
"type": "log_contains",
"pattern": "status=FOUND",
"fail_action": "fail"
},
{
"id": "deeplink_value_bg",
"description": "deepLinkValue matches qa_deeplink_bg",
"type": "log_contains",
"pattern": "deepLinkValue=qa_deeplink_bg",
"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 is in foreground after SDK start. On Android a brief HOME switch triggers onPause and the VIEW intent brings the app back. On iOS the app is terminated and re-launched with -deepLinkURL (same launch-arg path as phase_2); the foreground-vs-killed distinction is Android-only. Both paths fire onDeepLinking with status=FOUND.",
"requires_fresh_install": true,
"wait_after_launch_sec": 240,
"post_marker_wait_sec": 30,
"wait_after_trigger_sec": 90,
"deep_link_url": "afqa-unity://deeplink?deep_link_value=qa_deeplink_fg&af_sub1=foreground_test&pid=testmedia&c=deeplink_test",
"pre_actions": {
"android": ["adb shell am force-stop com.appsflyer.engagement", "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": "sdk_started",
"description": "startSDK returns SUCCESS on fresh install",
"type": "log_contains",
"pattern": "[AF_QA][startSDK] result: SUCCESS",
"fail_action": "abort"
},
{
"id": "install_conversion_callback",
"description": "onInstallConversionData callback fires before deep link",
"type": "log_contains",
"pattern": "[AF_QA][CALLBACK][onInstallConversionData]",
"fail_action": "abort"
},
{
"id": "deeplink_found_fg",
"description": "onDeepLinking fires with status=FOUND after foreground deep link",
"type": "log_contains",
"pattern": "status=FOUND",
"fail_action": "fail"
},
{
"id": "deeplink_value_fg",
"description": "deepLinkValue matches qa_deeplink_fg",
"type": "log_contains",
"pattern": "deepLinkValue=qa_deeplink_fg",
"fail_action": "fail"
},
{
"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": "Verifies the custom logEvent with multi-type parameters (revenue, currency, nested metadata string) fired by the test app's auto-run after cold launch. Covers E2E-004.",
"requires_fresh_install": false,
"wait_after_trigger_sec": 5,
"checks": [
{
"id": "custom_event_logged",
"description": "logEvent invocation with name af_qa_custom_purchase is logged",
"type": "log_contains",
"pattern": "[AF_QA][logEvent] name=af_qa_custom_purchase",
"fail_action": "fail"
},
{
"id": "param_revenue_present",
"description": "Revenue parameter present in serialized payload",
"type": "log_contains",
"pattern": "af_revenue",
"fail_action": "fail"
},
{
"id": "param_currency_present",
"description": "Currency parameter present",
"type": "log_contains",
"pattern": "af_currency",
"fail_action": "fail"
},
{
"id": "param_nested_metadata",
"description": "Nested metadata key preserved in payload",
"type": "log_contains",
"pattern": "metadata",
"fail_action": "fail"
},
{
"id": "custom_event_http_200",
"description": "Custom event request returns HTTP 200",
"type": "count_matches",
"pattern": "response code:200 OK|response_status=200|responseCode=200",
"minimum": 1,
"fail_action": "fail"
},
{
"id": "no_log_event_error",
"description": "No logEvent error reported",
"type": "absent",
"patterns": ["[AF_QA][logEvent] error:"],
"fail_action": "fail"
}
]
},

{
"id": "phase_5",
"name": "Identity APIs round-trip",
"scenario_ref": "E2E-005",
"description": "Fresh install. Sets customer user id (e2e_user_42), currency code (EUR), and additional data (tenant: qa_eu) before start. Verifies readback and propagation. Covers E2E-005.",
"requires_fresh_install": true,
"wait_after_launch_sec": 240,
"post_marker_wait_sec": 30,
"wait_after_trigger_sec": 5,
"checks": [
{
"id": "set_customer_user_id_logged",
"description": "setCustomerUserId readback present",
"type": "log_contains",
"pattern": "[AF_QA][setCustomerUserId] result: e2e_user_42",
"fail_action": "fail"
},
{
"id": "set_currency_code_logged",
"description": "setCurrencyCode readback present",
"type": "log_contains",
"pattern": "[AF_QA][setCurrencyCode] result: EUR",
"fail_action": "fail"
},
{
"id": "set_additional_data_logged",
"description": "setAdditionalData echoes its keys",
"type": "log_contains",
"pattern": "[AF_QA][setAdditionalData]",
"fail_action": "fail"
},
{
"id": "user_id_in_payload",
"description": "Post-start event payload carries customer_user_id",
"type": "regex_match",
"pattern": "customer_user_id[ =:]+e2e_user_42",
"fail_action": "fail"
},
{
"id": "additional_data_in_payload",
"description": "Post-start event payload contains additional data tenant key",
"type": "log_contains",
"pattern": "tenant",
"fail_action": "fail"
},
{
"id": "install_conversion_callback",
"description": "Install conversion 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": "identity_event_http_200",
"description": "Identity check event receives HTTP 200",
"type": "count_matches",
"pattern": "response code:200 OK|response_status=200|responseCode=200",
"minimum": 1,
"fail_action": "fail"
}
]
},

{
"id": "phase_6",
"name": "Consent / SDK stop toggle",
"scenario_ref": "E2E-006",
"description": "Auto-run dispatches stop(true), an event that must be suppressed, then stop(false), and an event that must succeed. Verifies the privacy kill switch. Covers E2E-006.",
"requires_fresh_install": false,
"wait_after_trigger_sec": 5,
"checks": [
{
"id": "stop_true_logged",
"description": "stop(true) readback present",
"type": "log_contains",
"pattern": "[AF_QA][stop] result: true",
"fail_action": "fail"
},
{
"id": "stop_false_logged",
"description": "stop(false) readback present",
"type": "log_contains",
"pattern": "[AF_QA][stop] result: false",
"fail_action": "fail"
},
{
"id": "suppressed_event_no_http",
"description": "While stopped, af_qa_suppressed must not produce an HTTP 200 response",
"type": "absent",
"patterns": ["af_qa_suppressed.*response code:200", "af_qa_suppressed.*response_status=200", "af_qa_suppressed.*responseCode=200"],
"fail_action": "fail"
},
{
"id": "resumed_event_http_200",
"description": "After stop(false), af_qa_resumed produces HTTP 200",
"type": "count_matches",
"pattern": "response code:200 OK|response_status=200|responseCode=200",
"minimum": 1,
"fail_action": "fail"
},
{
"id": "no_fatal_errors",
"description": "No fatal exceptions across the stop/start cycle",
"type": "absent",
"patterns": ["Fatal Exception", "FATAL"],
"fail_action": "fail"
}
]
}
],

"report": {
"output_dir": ".af-e2e/reports/",
"format": "json"
}
}
Loading
Loading