Skip to content

Commit dc755d5

Browse files
test(mac): implement controlled browser logout for Chrome 137 compatibility
Add controlled browser logout for macOS to handle deep-link dispatch issues introduced by Chrome 137 code-splitting changes. Changes: - Add logout_with_controlled_browser() in test_mac_helpers.py - Add get_logout_url_from_unity_logs() to monitor Unity logs - Add get_product_name() to extract product name from ProjectSettings - Update logout() method to use controlled browser approach - Manually trigger immutablerunner://logout deep-link via 'open' command - Reduce Windows login timeout from 90s to 30s This matches the Windows implementation and resolves logout timeouts caused by Chrome 137's increased code-splitting affecting protocol handler reliability.
1 parent 702b6fb commit dc755d5

3 files changed

Lines changed: 136 additions & 4 deletions

File tree

sample/Tests/test/test_mac.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from alttester import *
1717

1818
from test import TestConfig, UnityTest
19-
from test_mac_helpers import open_sample_app, bring_sample_app_to_foreground, stop_sample_app
19+
from test_mac_helpers import open_sample_app, bring_sample_app_to_foreground, stop_sample_app, logout_with_controlled_browser
2020

2121
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / 'src'))
2222
from fetch_otp import fetch_code
@@ -172,9 +172,19 @@ def logout(cls):
172172
print("Logging out...")
173173
cls.launch_browser()
174174
bring_sample_app_to_foreground()
175+
176+
# Click logout button
175177
cls.altdriver.find_object(By.NAME, "LogoutBtn").tap()
176-
time.sleep(5)
177-
bring_sample_app_to_foreground() # Bring app back to foreground after browser processes logout
178+
print("Logout button clicked")
179+
180+
# Use controlled browser to handle logout
181+
logout_with_controlled_browser()
182+
183+
# Bring app back to foreground after browser processes logout
184+
time.sleep(2)
185+
bring_sample_app_to_foreground()
186+
187+
# Wait for unauthenticated scene
178188
cls.altdriver.wait_for_current_scene_to_be("UnauthenticatedScene")
179189
time.sleep(2)
180190
cls.stop_browser()

sample/Tests/test/test_mac_helpers.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,133 @@
11
import subprocess
22
import time
33
import os
4+
import re
5+
from pathlib import Path
6+
from selenium import webdriver
7+
from selenium.webdriver.chrome.options import Options
48

59
def get_app_name():
610
"""Get the app name from environment variable, falling back to default"""
711
return os.getenv("UNITY_APP_NAME", "SampleApp")
812

13+
def get_product_name():
14+
"""Get the product name from ProjectSettings.asset"""
15+
project_settings_path = Path(__file__).resolve().parent.parent.parent / 'ProjectSettings' / 'ProjectSettings.asset'
16+
17+
if not project_settings_path.exists():
18+
print(f"Warning: ProjectSettings.asset not found at {project_settings_path}")
19+
return "SampleApp" # Fallback to default
20+
21+
with open(project_settings_path, 'r') as f:
22+
content = f.read()
23+
24+
# Extract productName using regex
25+
match = re.search(r'productName: (.+)', content)
26+
if match:
27+
product_name = match.group(1).strip()
28+
return product_name
29+
30+
# If regex fails, return default
31+
return "SampleApp"
32+
33+
def get_logout_url_from_unity_logs():
34+
"""Monitor Unity logs to capture logout URLs."""
35+
import tempfile
36+
37+
product_name = os.getenv("UNITY_APP_NAME", get_product_name())
38+
39+
# Unity log file locations on macOS
40+
log_paths = [
41+
os.path.join(os.path.expanduser("~"), "Library", "Logs", "Unity", product_name, "Player.log"),
42+
os.path.join(os.path.expanduser("~"), "Library", "Logs", product_name, "Player.log"),
43+
os.path.join(tempfile.gettempdir(), "UnityPlayer.log"),
44+
"Player.log" # Current directory
45+
]
46+
47+
for log_path in log_paths:
48+
if os.path.exists(log_path):
49+
print(f"Monitoring Unity log for logout URL: {log_path}")
50+
try:
51+
with open(log_path, 'r', encoding='utf-8', errors='ignore') as f:
52+
content = f.read()
53+
# Look for logout URLs in Unity logs (uses same PASSPORT_AUTH_URL pattern)
54+
# Now includes [Immutable] tag from PassportLogger
55+
matches = re.findall(r'(?:\[Immutable\] PASSPORT_AUTH_URL: |PASSPORT_AUTH_URL: |LaunchAuthURL : )(https?://[^\s]+)', content)
56+
if matches:
57+
# Get the last URL and make sure it's a logout URL
58+
for url in reversed(matches):
59+
if 'logout' in url or 'im-logged-out' in url:
60+
print(f"Found logout URL: {url}")
61+
return url
62+
except Exception as e:
63+
print(f"Error reading log file {log_path}: {e}")
64+
continue
65+
66+
print("No logout URL found in Unity logs")
67+
return None
68+
69+
def logout_with_controlled_browser():
70+
"""Handle logout using the controlled browser instance instead of letting Unity open its own browser."""
71+
print("Starting controlled logout process...")
72+
73+
# Set up Chrome WebDriver options to connect to the existing browser instance
74+
chrome_options = Options()
75+
chrome_options.add_experimental_option("debuggerAddress", "localhost:9222")
76+
77+
# Brave binary location on macOS
78+
brave_path = "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
79+
chrome_options.binary_location = brave_path
80+
81+
try:
82+
# Connect to the existing browser instance
83+
driver = webdriver.Chrome(options=chrome_options)
84+
print("Connected to existing browser for logout")
85+
86+
# Monitor Unity logs for logout URL
87+
print("Monitoring Unity logs for logout URL...")
88+
logout_url = None
89+
for attempt in range(15): # Try for 15 seconds (shorter timeout)
90+
logout_url = get_logout_url_from_unity_logs()
91+
if logout_url:
92+
break
93+
time.sleep(1)
94+
95+
if logout_url:
96+
print(f"Navigating controlled browser to logout URL: {logout_url}")
97+
driver.get(logout_url)
98+
99+
# Wait for logout page to load
100+
time.sleep(3)
101+
print("Logout completed in controlled browser")
102+
103+
# Check final page
104+
current_url = driver.current_url
105+
print(f"Final logout URL: {current_url}")
106+
107+
# Extract the deep-link from the redirect
108+
# Look for immutablerunner://logout in the response or extract from returnTo parameter
109+
if 'returnTo=' in logout_url:
110+
# Extract returnTo parameter
111+
match = re.search(r'returnTo=([^&]+)', logout_url)
112+
if match:
113+
from urllib.parse import unquote
114+
return_to = unquote(match.group(1))
115+
print(f"Extracted returnTo deep-link: {return_to}")
116+
117+
# Trigger the deep-link manually
118+
print(f"Triggering deep-link manually: {return_to}")
119+
subprocess.run(['open', return_to], check=False)
120+
time.sleep(2)
121+
122+
else:
123+
print("Could not find logout URL in Unity logs - logout may complete without browser interaction")
124+
125+
except Exception as e:
126+
print(f"Error during controlled logout: {e}")
127+
print("Logout may need to be handled by Unity directly")
128+
129+
print("Controlled logout process completed")
130+
9131
def open_sample_app():
10132
app_name = get_app_name()
11133
print(f"Opening Unity sample app ({app_name})...")

sample/Tests/test/test_windows.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def _perform_login(self):
123123
# Wait for authenticated screen
124124
# Default AltTester timeout for this command is ~20s; CI often needs longer,
125125
# especially when the browser auto-handles the deep-link without a dialog.
126-
self.get_altdriver().wait_for_current_scene_to_be("AuthenticatedScene", timeout=90)
126+
self.get_altdriver().wait_for_current_scene_to_be("AuthenticatedScene", timeout=30)
127127
stop_browser()
128128
print("[SUCCESS] Login successful")
129129

0 commit comments

Comments
 (0)