Purchase IAP e2e test suite (#5186 follow-up)#5189
Merged
Conversation
…instances Layer 1 of the purchase e2e suite. The existing PurchaseTest pinned a single cached Purchase via implementation.setInAppPurchase(), so it could not catch the #5186 regression where a per-instance receiptStore is invisible to the native postReceipt path (which arrives on a freshly-constructed instance, as every real port returns a new Purchase per getInAppPurchase() call). - TestCodenameOneImplementation: add an InAppPurchaseFactory hook so a test can reproduce the ports' fresh-instance-per-call behaviour. - PurchaseTest.testReceiptStoreSharedAcrossFreshPurchaseInstances: install the store on one instance, drive Purchase.postReceipt(...) (which uses a different instance), and assert submitReceipt fires and the queue drains. Verified: passes with the static-field fix; fails (expected 1 but was 0) when the field is reverted to per-instance. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Layer 5 (gating) + wiring Layer 1. Runs PurchaseTest (receipt sync state machine + the #5186 fresh-instance regression guard) on the JavaSE simulator, gated to the IAP surface: payment package, the iOS StoreKit observer + ZoozPurchase/IOSImplementation, the Android BillingSupport/IBillingSupport/ ZoozPurchase/CodenameOneActivity, the payment unit tests, and the (incoming) native test harness scripts. Modeled on identity-stack.yml. native-ios (hosted XCTest + StoreKitTest) and native-android (fake IBillingSupport instrumentation) jobs are added with their harnesses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
Hosted XCTest driving a *simulated* App Store purchase via StoreKitTest's SKTestSession on the simulator -- no sandbox account, no network. The purchase flows through the real SKPaymentQueue into Codename One's actual StoreKit observer (paymentQueue:updatedTransactions:), the generated Purchase.postReceipt, the receipt-sync engine, and the ReceiptStore the sample installs at startup. This is the iOS-level guard for #5186: the observer submits through a freshly constructed Purchase instance, so a recorded submission proves the store installed on a different instance is visible to it (shared/static receiptStore). - Sample app (HelloCodenameOne.kt) references com.codename1.payment.Purchase so the builder flips usesPurchaseAPI -> defines CN1_USE_STOREKIT + links StoreKit.framework, and installs RecordingReceiptStore. - RecordingReceiptStore forwards each submitted transactionId through the new PurchaseTestSink native interface; the iOS impl persists to NSUserDefaults (javase/android impls record in memory). The hosted XCTest reads it back. - Products.storekit + PurchaseStoreKitTests.m drive SKTestSession.buyProduct and poll the sink. install-native-purchase-tests.sh injects the test, links StoreKit/StoreKitTest, bundles the config, configures the hosted target; run-ios-purchase-tests.sh orchestrates xcodebuild test. - purchase-e2e.yml: add build-port + native-ios (macos-15) jobs. Verified locally on macOS (Xcode 26.3, iOS 17 simulator): TEST SUCCEEDED, with CN1SS:IAP:SUBMITTED emitted -- the StoreKitTest purchase reached the store. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There is no local Google Play Billing sandbox (no SKTestSession equivalent), so this exercises the bridge with a fake IBillingSupport rather than a real purchase. - CodenameOneActivity: add a minimal test-only injection seam (setBillingSupportTestOverride) consulted by getBillingSupport(). Needed because the generated <Main>Stub auto-overrides createBillingSupport() + isBillingEnabled() whenever the app uses the Purchase API (our sample does, for the iOS wiring), which would otherwise shadow any subclass override. Not used in production. - CN1TestBillingSupport (androidTest): fake whose initBilling()/purchase() synthesize a completed Play purchase via the static Purchase.postReceipt(...) -- exactly where BillingSupport.onPurchasesUpdated would -- driving the receipt- sync engine and the app's RecordingReceiptStore (logs CN1SS:IAP:SUBMITTED). - PurchaseBillingInstrumentationTest (androidTest): injects the fake, launches the app, and scrapes logcat for the marker. Android-side guard for #5186: postReceipt submits through a fresh Purchase instance, so the marker proves the store installed on a different instance is visible to it. - purchase-e2e.yml: add native-android job (emulator) running only this test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
Author
Collaborator
Author
|
Compared 95 screenshots: 95 matched. |
…ified Fixes to make the Android billing-bridge test pass on an emulator (verified locally: connectedDebugAndroidTest BUILD SUCCESSFUL on cn1Api34Arm / API 34): - codenameone_settings.properties: add a placeholder android.licenseKey. The sample referencing the Purchase API makes the Android builder require it (Play Billing); without it every Android build of the sample fails. The purchase test uses a fake IBillingSupport, so the key is never used to verify a real purchase -- it only needs to be defined. - HelloCodenameOne.kt: after installing RecordingReceiptStore, call synchronizeReceipts() to drain receipts enqueued before the store existed. The fake fires a purchase from the activity's onCreate, which can race ahead of the CN1 init() that installs the store; draining on install makes it deterministic (and is sensible real-app behavior). - CN1TestBillingSupport: log CN1SS:IAP_FAKE when firing, to aid CI diagnosis. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
Compared 124 screenshots: 124 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Collaborator
Author
|
Compared 124 screenshots: 124 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
…gress CI) Wiring IAP into the shared hellocodenameone sample broke the previously-green scripts-android workflow: referencing the Purchase API pulled com.android.billingclient into every Android build, and my purchase instrumentation test (copied into every build from device-runner-app/androidTest) ran inside the shared screenshot suite, crashing the instrumentation process. Phase 1 - stop the bleeding: - Revert HelloCodenameOne.kt + codenameone_settings.properties to master (no Purchase reference, no licenseKey) and remove the sample-side IAP files (PurchaseTestSink + impls, RecordingReceiptStore). - Move the Android purchase test out of the auto-copied device-runner-app/ androidTest into scripts/purchase-test-app/android-test-src so it no longer contaminates scripts-android. - Scope purchase-e2e.yml to the verified core-tests job for now. Kept: the core regression test (Layer 1, passing) and the harmless framework test seam CodenameOneActivity.setBillingSupportTestOverride. Phase 2 (next): a dedicated minimal IAP app built only by the purchase-e2e iOS+Android jobs, so the StoreKitTest + billing-bridge tests run with zero ripple to the shared sample's workflows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
Compared 124 screenshots: 124 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
…d-sample ripple) Replaces the shared-hellocodenameone approach (which broke scripts-android) with a dedicated minimal CN1 app at scripts/purchase-test-app/app that uses IAP and installs RecordingReceiptStore. Built only by the purchase-e2e native jobs, so StoreKit / Play Billing never ripple into the screenshot/notification workflows. - scripts/purchase-test-app/app: minimal common/ios/android CN1 app (package com.codenameone.examples.purchasetest, mainName PurchaseTestApp). common wires IAP (references Purchase, installs RecordingReceiptStore, drains pending); PurchaseTestSink native interface (iOS NSUserDefaults impl + javase/android in-memory). Placeholder android.licenseKey (fake billing; never used at runtime). Trimmed to ios+android profiles only. - scripts/purchase-test-app/android-test-src: CN1TestBillingSupport (fake IBillingSupport) + PurchaseBillingInstrumentationTest. The test injects the fake via the CodenameOneActivity seam, waits for the store-install marker, then drives purchase() explicitly (deterministic, no startup race) and scrapes logcat for CN1SS:IAP:SUBMITTED. - build-android-app.sh: add CN1_APP_DIR + CN1_ANDROID_TEST_SOURCE_DIR support and derive the androidTest package from the app's codename1.packageName (defaults preserve hellocodenameone behaviour exactly). build-ios-app.sh already supported CN1_APP_DIR. - purchase-e2e.yml: re-add native-ios (StoreKitTest) + native-android (billing-bridge) jobs, both building the dedicated app; fixed the android-emulator-runner cd+gradlew single-line invocation. Verified locally on macOS: iOS xcodebuild test TEST SUCCEEDED; Android connectedDebugAndroidTest BUILD SUCCESSFUL (cn1Api34Arm / API 34), both with CN1SS:IAP:SUBMITTED. scripts-android/-ios unaffected (shared sample reverted). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Follow-up to the #5186 fix (#5188): a path-gated end-to-end test suite for the In-App-Purchase / ReceiptStore stack, verified locally on macOS and green in CI.
Layers (all green)
PurchaseTestregression guard: installs the store on onePurchaseinstance, drives the staticPurchase.postReceipt(...)(which uses a different instance), asserts the receipt is submitted. Fails (expected 1 but was 0) without the #5186 static-receiptStorefix.SKTestSessiondrives a simulated App Store purchase through the realSKPaymentQueueobserver →Purchase.postReceipt→ receipt-sync →RecordingReceiptStore→ NSUserDefaults sink, read back by the test.IBillingSupportvia a framework test seam, then drivespurchase()→Purchase.postReceipt→RecordingReceiptStore, asserted via aCN1SS:IAP:SUBMITTEDlogcat marker.Each platform exercises the same #5186 guard: a receipt submitted through a freshly-constructed
Purchaseinstance still reaches the store installed on a different instance.Dedicated test app (zero ripple)
The native layers build a dedicated minimal CN1 app (
scripts/purchase-test-app/app) rather than the sharedhellocodenameonesample — wiring IAP into the shared sample pulled Play Billing into every Android build and crashed thescripts-androidscreenshot suite. The dedicated app is built only by thenative-ios/native-androidpurchase-e2e jobs, so StoreKit / Play Billing never ripple into other workflows.build-android-app.sh/build-ios-app.shtake an app dir viaCN1_APP_DIR(defaults preservehellocodenameoneexactly).Framework change
CodenameOneActivity.setBillingSupportTestOverride(...)— a tiny, test-only injection point (the generated stub shadowscreateBillingSupport()once the Purchase API is used). Not used in production.CI
.github/workflows/purchase-e2e.yml, path-gated to the IAP surface:core-tests(container) +native-ios(macos) +native-android(emulator). Verified green, andBuild Android/scripts-iosremain unaffected.🤖 Generated with Claude Code