mirror of
https://github.com/yattee/yattee.git
synced 2026-02-20 17:59:45 +00:00
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).
141 lines
4.4 KiB
Ruby
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
|