From 94b0438601ea446d0d57a384a5b259c42ed20395 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Thu, 15 May 2025 17:40:39 +1200 Subject: [PATCH 1/2] test: fix build executable name mismatch --- sample/Assets/Editor/WindowsBuilder.cs | 2 +- sample/Tests/test/test_windows_helpers.py | 64 +++++++++++++------ .../Private/Helpers/WindowsDeepLink.cs | 2 +- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/sample/Assets/Editor/WindowsBuilder.cs b/sample/Assets/Editor/WindowsBuilder.cs index 833d243a..1574af99 100644 --- a/sample/Assets/Editor/WindowsBuilder.cs +++ b/sample/Assets/Editor/WindowsBuilder.cs @@ -8,7 +8,7 @@ public class WindowsBuilder { - private const string DefaultBuildPath = "Builds/Windows64/SampleApp.exe"; + private static readonly string DefaultBuildPath = $"Builds/Windows64/{Application.productName}.exe"; static void Build() { diff --git a/sample/Tests/test/test_windows_helpers.py b/sample/Tests/test/test_windows_helpers.py index f4a075bd..527e8b8c 100644 --- a/sample/Tests/test/test_windows_helpers.py +++ b/sample/Tests/test/test_windows_helpers.py @@ -1,6 +1,7 @@ import os -import sys +import re import subprocess +import sys import time from pathlib import Path @@ -18,6 +19,26 @@ # Add chrome.exe to environment variable # Download chrome driver and add to environment variable +def get_product_name(): + """Get the product name from ProjectSettings.asset""" + project_settings_path = Path(__file__).resolve().parent.parent.parent / 'ProjectSettings' / 'ProjectSettings.asset' + + if not project_settings_path.exists(): + print(f"Warning: ProjectSettings.asset not found at {project_settings_path}") + return "SampleApp" # Fallback to default + + with open(project_settings_path, 'r') as f: + content = f.read() + + # Extract productName using regex + match = re.search(r'productName: (.+)', content) + if match: + product_name = match.group(1).strip() + return product_name + + # If regex fails, return default + return "SampleApp" + def login(): print("Connect to Chrome") # Set up Chrome options to connect to the existing Chrome instance @@ -80,37 +101,40 @@ def login(): driver.quit() def open_sample_app(): - print("Opening Unity sample app...") - subprocess.Popen(["SampleApp.exe"], shell=True) + product_name = get_product_name() + print(f"Opening {product_name}...") + subprocess.Popen([f"{product_name}.exe"], shell=True) time.sleep(10) - print("Unity sample app opened successfully.") + print(f"{product_name} opened successfully.") def stop_sample_app(): - print("Stopping sample app...") - powershell_command = """ - $process = Get-Process -Name "SampleApp" -ErrorAction SilentlyContinue - if ($process) { + product_name = get_product_name() + print(f"Stopping {product_name}...") + powershell_command = f""" + $process = Get-Process -Name "{product_name}" -ErrorAction SilentlyContinue + if ($process) {{ Stop-Process -Id $process.Id - Write-Output "SampleApp.exe has been closed." - } else { - Write-Output "SampleApp.exe is not running." - } + Write-Output "{product_name}.exe has been closed." + }} else {{ + Write-Output "{product_name}.exe is not running." + }} """ subprocess.run(["powershell.exe", "-Command", powershell_command], check=True) time.sleep(5) - print("Stopped sample app.") + print(f"{product_name} stopped successfully.") def bring_sample_app_to_foreground(): + product_name = get_product_name() powershell_script_path = "./switch-app.ps1" - - print("Bring Unity sample app to the foreground.") - + + print(f"Bring {product_name} to the foreground.") + command = [ - "powershell.exe", - "-Command", - f"Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process; & '{powershell_script_path}' -appName 'Immutable Sample'" + "powershell.exe", + "-Command", + f"Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process; & '{powershell_script_path}' -appName '{product_name}'" ] - + subprocess.run(command, check=True) time.sleep(10) diff --git a/src/Packages/Passport/Runtime/Scripts/Private/Helpers/WindowsDeepLink.cs b/src/Packages/Passport/Runtime/Scripts/Private/Helpers/WindowsDeepLink.cs index 1c55a719..23cd8e82 100644 --- a/src/Packages/Passport/Runtime/Scripts/Private/Helpers/WindowsDeepLink.cs +++ b/src/Packages/Passport/Runtime/Scripts/Private/Helpers/WindowsDeepLink.cs @@ -261,7 +261,7 @@ private static string GetGameExecutablePath(string suffix) return Path.Combine(Application.persistentDataPath, exeName).Replace("/", "\\"); #else // Returns game root directory in build - var exePath = Application.dataPath.Replace("/Data", "").Replace($"/{Application.productName}_Data", ""); + var exePath = Path.Combine(Application.dataPath, "../"); return Path.Combine(exePath, exeName).Replace("/", "\\"); #endif } From 4b97b5d19b1eece6af10fd2cc265ab8e9f4b1f6a Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Thu, 15 May 2025 17:47:41 +1200 Subject: [PATCH 2/2] test: add pkce login test for windows --- sample/Tests/test/test.py | 17 ++- sample/Tests/test/test_windows.py | 120 +++++++++++----------- sample/Tests/test/test_windows_helpers.py | 16 +-- 3 files changed, 83 insertions(+), 70 deletions(-) diff --git a/sample/Tests/test/test.py b/sample/Tests/test/test.py index a03a214e..6f46ed98 100644 --- a/sample/Tests/test/test.py +++ b/sample/Tests/test/test.py @@ -12,7 +12,7 @@ class TestConfig: WALLET_ADDRESS = "0x547044ea95f03651139081241c99ffedbefdc5e8" ANDROID_PACKAGE = "com.immutable.ImmutableSample" IOS_BUNDLE_ID = "com.immutable.Immutable-Sample-GameSDK" - + class UnityTest(unittest.TestCase): altdriver = None @@ -23,7 +23,18 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - cls.altdriver.stop() + if cls.altdriver: + cls.altdriver.stop() + + def get_altdriver(self): + return self.__class__.altdriver + + def start_altdriver(self): + self.__class__.altdriver = AltDriver() + + def stop_altdriver(self): + if self.__class__.altdriver: + self.__class__.altdriver.stop() @pytest.mark.skip(reason="Base test should not be executed directly") def test_0_other_functions(self): @@ -266,7 +277,7 @@ def test_3_zkevm_functions(self): transactionHash = match.group() else: raise SystemExit(f"Could not find transaction hash") - + # Go back to authenticated scene self.altdriver.find_object(By.NAME, "CancelButton").tap() self.altdriver.wait_for_current_scene_to_be("AuthenticatedScene") diff --git a/sample/Tests/test/test_windows.py b/sample/Tests/test/test_windows.py index aeef3358..2b04fc53 100644 --- a/sample/Tests/test/test_windows.py +++ b/sample/Tests/test/test_windows.py @@ -7,29 +7,38 @@ class WindowsTest(UnityTest): - altdriver = None - @classmethod def setUpClass(cls): open_sample_app() - cls.altdriver = AltDriver() + time.sleep(5) # Give time for the app to open + super().setUpClass() @classmethod def tearDownClass(cls): - cls.altdriver.stop() - stop_sample_app() + super().tearDownClass() + stop_sample_app() - def test_1_device_code_login(self): - # Select use device code auth - self.altdriver.find_object(By.NAME, "DeviceCodeAuth").tap() + def restart_app_and_altdriver(self): + self.stop_altdriver() + stop_sample_app() + open_sample_app() + time.sleep(5) # Give time for the app to open + self.start_altdriver() + + def select_auth_type(self, use_pkce: bool): + auth_type = "PKCE" if use_pkce else "DeviceCodeAuth" + self.get_altdriver().find_object(By.NAME, auth_type).tap() + + def login(self, use_pkce: bool): + self.select_auth_type(use_pkce) # Wait for unauthenticated screen - self.altdriver.wait_for_current_scene_to_be("UnauthenticatedScene") + self.get_altdriver().wait_for_current_scene_to_be("UnauthenticatedScene") for attempt in range(2): try: # Check app state - login_button = self.altdriver.find_object(By.NAME, "LoginBtn") + login_button = self.get_altdriver().find_object(By.NAME, "LoginBtn") print("Found login button, app is in the correct state") # Login @@ -37,11 +46,11 @@ def test_1_device_code_login(self): launch_browser() bring_sample_app_to_foreground() login_button.tap() - login() + login(use_pkce) bring_sample_app_to_foreground() # Wait for authenticated screen - self.altdriver.wait_for_current_scene_to_be("AuthenticatedScene") + self.get_altdriver().wait_for_current_scene_to_be("AuthenticatedScene") stop_browser() print("Logged in") return @@ -54,24 +63,24 @@ def test_1_device_code_login(self): # Relogin (optional: only if the button is present) print("Try reset the app and log out once...") try: - self.altdriver.wait_for_object(By.NAME, "ReloginBtn").tap() + self.get_altdriver().wait_for_object(By.NAME, "ReloginBtn").tap() except Exception as e: print("ReloginBtn not found, skipping relogin step. User may already be in AuthenticatedScene.") # Wait for authenticated screen - self.altdriver.wait_for_current_scene_to_be("AuthenticatedScene") + self.get_altdriver().wait_for_current_scene_to_be("AuthenticatedScene") print("Re-logged in") # Logout print("Logging out...") launch_browser() bring_sample_app_to_foreground() - self.altdriver.find_object(By.NAME, "LogoutBtn").tap() + self.get_altdriver().find_object(By.NAME, "LogoutBtn").tap() time.sleep(5) bring_sample_app_to_foreground() - + # Wait for unauthenticated screen - self.altdriver.wait_for_current_scene_to_be("UnauthenticatedScene") + self.get_altdriver().wait_for_current_scene_to_be("UnauthenticatedScene") stop_browser() print("Logged out and successfully reset app") @@ -79,6 +88,13 @@ def test_1_device_code_login(self): else: raise SystemExit(f"Failed to reset app {err}") + def test_1a_pkce_login(self): + self.login(True) + + def test_1b_device_code_login(self): + self.restart_app_and_altdriver() + self.login(False) + def test_2_other_functions(self): self.test_0_other_functions() @@ -92,77 +108,61 @@ def test_5_zkevm_functions(self): self.test_3_zkevm_functions() def test_6_relogin(self): - # Close and reopen app - stop_sample_app() - open_sample_app() - - # Restart AltTester - self.altdriver.stop() - self.altdriver = AltDriver() - time.sleep(5) + self.restart_app_and_altdriver() # Select use device code auth - self.altdriver.find_object(By.NAME, "DeviceCodeAuth").tap() + self.select_auth_type(use_pkce=False) # Relogin print("Re-logging in...") - self.altdriver.wait_for_object(By.NAME, "ReloginBtn").tap() + self.get_altdriver().wait_for_object(By.NAME, "ReloginBtn").tap() # Wait for authenticated screen - self.altdriver.wait_for_current_scene_to_be("AuthenticatedScene") + self.get_altdriver().wait_for_current_scene_to_be("AuthenticatedScene") print("Re-logged in") # Get access token - self.altdriver.find_object(By.NAME, "GetAccessTokenBtn").tap() - output = self.altdriver.find_object(By.NAME, "Output") + self.get_altdriver().find_object(By.NAME, "GetAccessTokenBtn").tap() + output = self.get_altdriver().find_object(By.NAME, "Output") self.assertTrue(len(output.get_text()) > 50) # Click Connect to IMX button - self.altdriver.find_object(By.NAME, "ConnectBtn").tap() + self.get_altdriver().find_object(By.NAME, "ConnectBtn").tap() self.assertEqual("Connected to IMX", output.get_text()) - self.altdriver.stop() - def test_7_reconnect_device_code_connect_imx(self): - # Close and reopen app - stop_sample_app() - open_sample_app() + self.restart_app_and_altdriver() - # Restart AltTester - self.altdriver.stop() - self.altdriver = AltDriver() - time.sleep(5) - - # Select use device code auth - self.altdriver.find_object(By.NAME, "DeviceCodeAuth").tap() + use_pkce = False + self.select_auth_type(use_pkce) # Reconnect print("Reconnecting...") - self.altdriver.wait_for_object(By.NAME, "ReconnectBtn").tap() + self.get_altdriver().wait_for_object(By.NAME, "ReconnectBtn").tap() # Wait for authenticated screen - self.altdriver.wait_for_current_scene_to_be("AuthenticatedScene") + self.get_altdriver().wait_for_current_scene_to_be("AuthenticatedScene") print("Reconnected") # Get access token - self.altdriver.find_object(By.NAME, "GetAccessTokenBtn").tap() - output = self.altdriver.find_object(By.NAME, "Output") + self.get_altdriver().find_object(By.NAME, "GetAccessTokenBtn").tap() + output = self.get_altdriver().find_object(By.NAME, "Output") self.assertTrue(len(output.get_text()) > 50) # Get address without having to click Connect to IMX button - self.altdriver.find_object(By.NAME, "GetAddressBtn").tap() + self.get_altdriver().find_object(By.NAME, "GetAddressBtn").tap() self.assertEqual(TestConfig.WALLET_ADDRESS, output.get_text()) # Logout print("Logging out...") launch_browser() bring_sample_app_to_foreground() - self.altdriver.find_object(By.NAME, "LogoutBtn").tap() + self.get_altdriver().find_object(By.NAME, "LogoutBtn").tap() time.sleep(5) bring_sample_app_to_foreground() # Wait for authenticated screen - self.altdriver.wait_for_current_scene_to_be("UnauthenticatedScene") + self.get_altdriver().wait_for_current_scene_to_be("UnauthenticatedScene") stop_browser() print("Logged out") @@ -170,33 +170,33 @@ def test_7_reconnect_device_code_connect_imx(self): print("Logging in and connecting to IMX...") launch_browser() bring_sample_app_to_foreground() - self.altdriver.wait_for_object(By.NAME, "ConnectBtn").tap() - login() + self.get_altdriver().wait_for_object(By.NAME, "ConnectBtn").tap() + login(use_pkce) bring_sample_app_to_foreground() - + # Wait for authenticated screen - self.altdriver.wait_for_current_scene_to_be("AuthenticatedScene") + self.get_altdriver().wait_for_current_scene_to_be("AuthenticatedScene") print("Logged in and connected to IMX") stop_browser() # Get access token - self.altdriver.find_object(By.NAME, "GetAccessTokenBtn").tap() - output = self.altdriver.find_object(By.NAME, "Output") + self.get_altdriver().find_object(By.NAME, "GetAccessTokenBtn").tap() + output = self.get_altdriver().find_object(By.NAME, "Output") self.assertTrue(len(output.get_text()) > 50) # Get address without having to click Connect to IMX button - self.altdriver.find_object(By.NAME, "GetAddressBtn").tap() + self.get_altdriver().find_object(By.NAME, "GetAddressBtn").tap() self.assertEqual(TestConfig.WALLET_ADDRESS, output.get_text()) # Logout launch_browser() bring_sample_app_to_foreground() print("Logging out...") - self.altdriver.find_object(By.NAME, "LogoutBtn").tap() + self.get_altdriver().find_object(By.NAME, "LogoutBtn").tap() time.sleep(5) bring_sample_app_to_foreground() - + # Wait for authenticated screen - self.altdriver.wait_for_current_scene_to_be("UnauthenticatedScene") + self.get_altdriver().wait_for_current_scene_to_be("UnauthenticatedScene") stop_browser() print("Logged out") diff --git a/sample/Tests/test/test_windows_helpers.py b/sample/Tests/test/test_windows_helpers.py index 527e8b8c..778ef5e0 100644 --- a/sample/Tests/test/test_windows_helpers.py +++ b/sample/Tests/test/test_windows_helpers.py @@ -39,7 +39,7 @@ def get_product_name(): # If regex fails, return default return "SampleApp" -def login(): +def login(use_pkce: bool): print("Connect to Chrome") # Set up Chrome options to connect to the existing Chrome instance chrome_options = Options() @@ -62,13 +62,14 @@ def login(): print("Switch to the new window") driver.switch_to.window(new_window) - + wait = WebDriverWait(driver, 60) - print("Wait for device confirmation...") - contine_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[span[text()='Continue']]"))) - contine_button.click() - print("Confirmed device") + if not use_pkce: + print("Wait for device confirmation...") + contine_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[span[text()='Continue']]"))) + contine_button.click() + print("Confirmed device") print("Wait for email input...") email_field = wait.until(EC.presence_of_element_located((By.ID, ':r1:'))) @@ -95,7 +96,8 @@ def login(): otp_field.send_keys(code) print("Wait for success page...") - success = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'h1[data-testid="device_success_title"]'))) + success_title = 'h1[data-testid="checking_title"]' if use_pkce else 'h1[data-testid="device_success_title"]' + wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, success_title))) print("Connected to Passport!") driver.quit()