Files
yattee/spec/ui/support/simulator.rb
Arkadiusz Fal cae1226cfe Add URL scheme UI tests for deep link navigation
Test yattee:// custom scheme URLs navigate to correct screens:
playlists, bookmarks, history, downloads, channels, subscriptions,
continue-watching, and search. Handles iOS system confirmation dialog
via coordinate taps since it's invisible to AXe. Settings deep link
is excluded (known app bug - doesn't render when pushed to nav stack).
2026-02-10 05:45:19 +01:00

141 lines
4.4 KiB
Ruby

# frozen_string_literal: true
require 'open3'
require 'json'
module UITest
# Manages iOS Simulator lifecycle
class Simulator
class SimulatorError < StandardError; end
class << self
# Boot a simulator by device name and return its UDID
# @param device_name [String] Name of the device (e.g., "iPhone 17 Pro")
# @return [String] UDID of the booted simulator
def boot(device_name)
udid = find_udid(device_name)
raise SimulatorError, "Simulator '#{device_name}' not found" unless udid
status = device_status(udid)
if status == 'Shutdown'
puts "Booting simulator '#{device_name}'..."
run_simctl('boot', udid)
wait_until_booted(udid)
elsif status == 'Booted'
puts "Simulator '#{device_name}' is already booted"
else
puts "Simulator '#{device_name}' is in state '#{status}', waiting..."
wait_until_booted(udid)
end
# Set consistent status bar for reproducible screenshots
set_status_bar_overrides(udid)
udid
end
# Open a URL in the simulator (triggers deep link handling)
# @param udid [String] UDID of the simulator
# @param url [String] URL to open
def open_url(udid, url)
output, status = Open3.capture2e('xcrun', 'simctl', 'openurl', udid, url)
raise SimulatorError, "openurl failed: #{output}" unless status.success?
end
# Shutdown a simulator by UDID
# @param udid [String] UDID of the simulator
def shutdown(udid)
return unless udid
status = device_status(udid)
return if status == 'Shutdown'
clear_status_bar_overrides(udid)
puts 'Shutting down simulator...'
run_simctl('shutdown', udid)
end
# Set status bar overrides for consistent screenshots
# Uses Apple's iconic 9:41 time and full signal/battery
# @param udid [String] UDID of the simulator
def set_status_bar_overrides(udid)
puts 'Setting status bar overrides for consistent screenshots...'
run_simctl(
'status_bar', udid, 'override',
'--time', '9:41',
'--batteryState', 'charged',
'--batteryLevel', '100',
'--wifiBars', '3',
'--cellularBars', '4'
)
end
# Clear status bar overrides
# @param udid [String] UDID of the simulator
def clear_status_bar_overrides(udid)
run_simctl('status_bar', udid, 'clear')
rescue SimulatorError
# Ignore errors when clearing (simulator may already be shut down)
nil
end
# Find UDID for a device by name
# @param device_name [String] Name of the device
# @return [String, nil] UDID or nil if not found
def find_udid(device_name)
output, status = Open3.capture2('xcrun', 'simctl', 'list', 'devices', 'available', '-j')
raise SimulatorError, 'Failed to list simulators' unless status.success?
data = JSON.parse(output)
devices = data['devices'].values.flatten
# Find exact match first
device = devices.find { |d| d['name'] == device_name }
device&.fetch('udid', nil)
end
# Get device status by UDID
# @param udid [String] UDID of the simulator
# @return [String] Status (e.g., "Booted", "Shutdown")
def device_status(udid)
output, status = Open3.capture2('xcrun', 'simctl', 'list', 'devices', '-j')
raise SimulatorError, 'Failed to get device status' unless status.success?
data = JSON.parse(output)
devices = data['devices'].values.flatten
device = devices.find { |d| d['udid'] == udid }
device&.fetch('state', 'Unknown') || 'Unknown'
end
# Wait until simulator is fully booted
# @param udid [String] UDID of the simulator
# @param timeout [Integer] Timeout in seconds
def wait_until_booted(udid, timeout: 60)
start_time = Time.now
loop do
status = device_status(udid)
return if status == 'Booted'
if Time.now - start_time > timeout
raise SimulatorError, "Timeout waiting for simulator to boot (status: #{status})"
end
sleep 1
end
end
private
def run_simctl(*args)
output, status = Open3.capture2e('xcrun', 'simctl', *args)
raise SimulatorError, "simctl #{args.first} failed: #{output}" unless status.success?
output
end
end
end
end