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
105 changes: 105 additions & 0 deletions e2e-tests/MaestroTestApp/Assets/Editor/BuildScript.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using UnityEditor;
using UnityEditor.Build.Reporting;
using System;
using System.IO;
using System.Linq;

static class BuildScript
{
private static readonly string BuildPathIOS = "build/ios";
private static readonly string BuildPathAndroid = "build/android/MaestroTestApp.apk";

static string[] GetEnabledScenes()
{
return (
from scene in EditorBuildSettings.scenes
where scene.enabled
where !string.IsNullOrEmpty(scene.path)
select scene.path
).ToArray();
}

[MenuItem("Build/Build iOS")]
public static void BuildIOS()
{
var scenes = GetEnabledScenes();
if (scenes.Length == 0)
{
Console.WriteLine(":: No scenes found in EditorBuildSettings, looking for scene files...");
scenes = Directory.GetFiles("Assets/Scenes", "*.unity", SearchOption.AllDirectories);
}

if (scenes.Length == 0)
{
throw new Exception("No scenes found to build.");
}

Console.WriteLine(":: Building iOS with scenes:");
foreach (var scene in scenes)
{
Console.WriteLine(":: " + scene);
}

var buildPlayerOptions = new BuildPlayerOptions
{
scenes = scenes,
locationPathName = BuildPathIOS,
target = BuildTarget.iOS,
options = BuildOptions.None
};

var report = BuildPipeline.BuildPlayer(buildPlayerOptions);

if (report.summary.result != BuildResult.Succeeded)
{
throw new Exception("Build failed with " + report.summary.totalErrors + " error(s)");
}

Console.WriteLine(":: Build succeeded. Output: " + BuildPathIOS);
}

[MenuItem("Build/Build Android")]
public static void BuildAndroid()
{
var scenes = GetEnabledScenes();
if (scenes.Length == 0)
{
Console.WriteLine(":: No scenes found in EditorBuildSettings, looking for scene files...");
scenes = Directory.GetFiles("Assets/Scenes", "*.unity", SearchOption.AllDirectories);
}

if (scenes.Length == 0)
{
throw new Exception("No scenes found to build.");
}

Console.WriteLine(":: Building Android with scenes:");
foreach (var scene in scenes)
{
Console.WriteLine(":: " + scene);
}

var buildDir = Path.GetDirectoryName(BuildPathAndroid);
if (!Directory.Exists(buildDir))
{
Directory.CreateDirectory(buildDir);
}

var buildPlayerOptions = new BuildPlayerOptions
{
scenes = scenes,
locationPathName = BuildPathAndroid,
target = BuildTarget.Android,
options = BuildOptions.None
};

var report = BuildPipeline.BuildPlayer(buildPlayerOptions);

if (report.summary.result != BuildResult.Succeeded)
{
throw new Exception("Build failed with " + report.summary.totalErrors + " error(s)");
}

Console.WriteLine(":: Build succeeded. Output: " + BuildPathAndroid);
}
}
103 changes: 103 additions & 0 deletions e2e-tests/MaestroTestApp/Assets/Scripts/MaestroTestApp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using UnityEngine;
using UnityEngine.UI;
using RevenueCatUI;

public class MaestroTestApp : Purchases.UpdatedCustomerInfoListener
{
private const string API_KEY = "MAESTRO_TESTS_REVENUECAT_API_KEY";

public GameObject testCasesScreen;
public GameObject purchaseScreen;
public Text entitlementsLabel;
public Text errorLabel;

private Purchases purchases;

void Start()
{
purchases = GetComponent<Purchases>();
purchases.useRuntimeSetup = true;

var config = Purchases.PurchasesConfiguration.Builder.Init(API_KEY).Build();
purchases.Configure(config);
purchases.SetLogLevel(Purchases.LogLevel.Verbose);
purchases.listener = this;

if (errorLabel != null)
{
errorLabel.gameObject.SetActive(false);
}

ShowTestCases();
}

public void ShowTestCases()
{
testCasesScreen.SetActive(true);
purchaseScreen.SetActive(false);
}

public void ShowPurchaseScreen()
{
testCasesScreen.SetActive(false);
purchaseScreen.SetActive(true);
ClearError();
UpdateEntitlements();
}

public async void PresentPaywall()
{
ClearError();
try
{
await PaywallsPresenter.Present();
}
catch (System.Exception e)
{
Debug.LogError($"Failed to present paywall: {e}");
ShowError(e.Message);
}
}

public override void CustomerInfoReceived(CustomerInfo customerInfo)
{
UpdateEntitlementsFromInfo(customerInfo);
}

private void UpdateEntitlements()
{
purchases.GetCustomerInfo((info, error) =>
{
if (info != null)
{
UpdateEntitlementsFromInfo(info);
}
});
}

private void UpdateEntitlementsFromInfo(CustomerInfo info)
{
bool hasPro = info.Entitlements.Active.ContainsKey("pro");
if (entitlementsLabel != null)
{
entitlementsLabel.text = "Entitlements: " + (hasPro ? "pro" : "none");
}
}

private void ShowError(string message)
{
if (errorLabel != null)
{
errorLabel.text = "Error: " + message;
errorLabel.gameObject.SetActive(true);
}
}

private void ClearError()
{
if (errorLabel != null)
{
errorLabel.gameObject.SetActive(false);
}
}
}
45 changes: 45 additions & 0 deletions e2e-tests/MaestroTestApp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Maestro E2E Test App

A minimal Unity app used by Maestro end-to-end tests to verify RevenueCat SDK integration.

## Prerequisites

- Unity Editor (with iOS Build Support and/or Android Build Support modules)
- Xcode (iOS) / Android Studio (Android)
- [Maestro](https://maestro.mobile.dev/) CLI

## Setup

This project requires manual scene setup in the Unity Editor because `.unity` scene
files cannot be created outside the editor. See [setup-instructions.md](./setup-instructions.md)
for detailed step-by-step instructions.

## Running Locally

1. Open the project in Unity Editor
2. File > Build Settings > select iOS or Android
3. Build and Run

## API Key

The app initialises RevenueCat with the placeholder `MAESTRO_TESTS_REVENUECAT_API_KEY`.
In CI, the Fastlane lane replaces this placeholder with the real key from the
`RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE` environment variable (provided by the
CircleCI `e2e-tests` context) before building.

To run locally, either:
- Replace the placeholder in `Assets/Scripts/MaestroTestApp.cs` with a valid API key
(do **not** commit it), or
- Export the env var and run the same `sed` command the Fastlane lane uses.

## RevenueCat Project

The test uses a RevenueCat project configured with:
- A **V2 Paywall** (the test asserts "Paywall V2" is visible)
- A `pro` entitlement (the test checks entitlement status after purchase)
- The **Test Store** environment for purchase confirmation

## Dependencies

The RevenueCat and RevenueCatUI Unity packages must be imported into the project
manually via the Unity Package Manager.
72 changes: 72 additions & 0 deletions e2e-tests/MaestroTestApp/setup-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# MaestroTestApp Unity Scene Setup

This app requires manual setup in the Unity Editor because `.unity` scene files
cannot be created outside the editor.

## Prerequisites

- Import the `RevenueCat` and `RevenueCatUI` packages into the project.

## Scene Setup (MainScene)

### 1. Create a Canvas

- GameObject > UI > Canvas
- Set Canvas Scaler to "Scale With Screen Size", Reference Resolution 375x812.

### 2. Add the Purchases component

- Create an empty GameObject named "Purchases".
- Add the `Purchases` MonoBehaviour component to it.
- Add the `MaestroTestApp` script to the same GameObject.
- In the Inspector, check "Use Runtime Setup" on the Purchases component.

### 3. Create testCasesScreen (child of Canvas)

- Create an empty GameObject named "TestCasesScreen" under the Canvas.
- Add a RectTransform that fills the entire Canvas.
- Add a child **Text** (UI > Legacy > Text):
- Text content: `Test Cases`
- Font size: 24, bold, centered horizontally, near the top.
- Add a child **Button** (UI > Legacy > Button):
- Set the button's child Text to: `Purchase through paywall`
- On Click: drag the Purchases GameObject, select `MaestroTestApp > ShowPurchaseScreen`.

### 4. Create purchaseScreen (child of Canvas)

- Create an empty GameObject named "PurchaseScreen" under the Canvas.
- Add a RectTransform that fills the entire Canvas.
- Set it to **inactive** by default (uncheck the checkbox at the top of Inspector).
- Add a child **Text** named "EntitlementsLabel" (UI > Legacy > Text):
- Text content: `Entitlements: none`
- Font size: 16, centered.
- Add a child **Button** (UI > Legacy > Button):
- Set the button's child Text to: `Present Paywall`
- On Click: drag the Purchases GameObject, select `MaestroTestApp > PresentPaywall`.
- Add a child **Text** named "ErrorLabel" (UI > Legacy > Text):
- Text content: (leave empty)
- Font size: 14, color red, centered.
- Set it to **inactive** by default.
- Add a child **Button** for back navigation:
- Set the button's child Text to: `Back`
- On Click: drag the Purchases GameObject, select `MaestroTestApp > ShowTestCases`.

### 5. Wire references in MaestroTestApp Inspector

- Drag "TestCasesScreen" to the `testCasesScreen` field.
- Drag "PurchaseScreen" to the `purchaseScreen` field.
- Drag "EntitlementsLabel" Text to the `entitlementsLabel` field.
- Drag "ErrorLabel" Text to the `errorLabel` field.

### 6. Project Settings

- Edit > Project Settings > Player:
- Set Bundle Identifier to: `com.revenuecat.automatedsdktests`
- Set Product Name to: `MaestroTestApp`

## CRITICAL: UI text must match exactly

- Title: `Test Cases`
- Test case button: `Purchase through paywall`
- Entitlements label: `Entitlements: none` (before purchase) / `Entitlements: pro` (after)
- Paywall button: `Present Paywall`