Files
yattee/spec/ui/support/config.rb
Arkadiusz Fal 56cd60a8ba Add UI smoke tests for Invidious behind HTTP Basic Auth
Three end-to-end specs that exercise the new basic-auth flows against a
real Invidious instance fronted by an nginx reverse proxy:

  1. add flow: types the URL, hits Detect, fills the basic-auth fields
     when the basicAuthRequired UI state appears, taps Retry Detection,
     and confirms the instance lands in the Sources list.

  2. state assertion: types a URL, taps Detect, and verifies the form
     transitions into the basicAuthRequired state (Retry Detection button
     present, no detected type) when no credentials were supplied.

  3. proxied login: ensures the instance exists, then drives the standard
     Invidious login flow with the proxied account credentials. Confirms
     the SID Cookie auth coexists with the per-client Authorization
     header on the basic-auth-aware HTTPClient.

Test infrastructure additions:

- spec/ui/support/config.rb: env-driven accessors for the basic-auth URL
  and proxied-account credentials. No secrets committed.

- spec/ui/support/instance_setup.rb: helpers
  add_invidious_with_basic_auth, remove_and_add_invidious_with_basic_auth,
  find_basic_auth_text_fields (mirroring find_auth_text_fields), and
  fill_field for tapping a discovered field by frame and typing into it.

All three specs skip cleanly when the relevant env vars are not set.
2026-04-18 20:38:01 +02:00

284 lines
7.7 KiB
Ruby

# frozen_string_literal: true
module UITest
# Configuration for UI tests
class Config
class << self
# Device name for simulator (from env or default)
def device
ENV.fetch('UI_TEST_DEVICE', 'iPhone 17 Pro')
end
# Sanitized device name for file paths (replaces spaces and special chars)
def device_slug
device.gsub(/[^a-zA-Z0-9]/, '_')
end
# iOS version of the selected simulator
def ios_version
@ios_version ||= detect_ios_version
end
# Sanitized iOS version for file paths (e.g., "18_1")
def ios_version_slug
ios_version.gsub('.', '_')
end
# Combined device and iOS version slug for snapshot directories
def snapshot_slug
"#{device_slug}/iOS_#{ios_version_slug}"
end
private
# Detect iOS version from the simulator runtime
def detect_ios_version
require 'json'
output = `xcrun simctl list devices available -j 2>/dev/null`
data = JSON.parse(output)
# Find the device and extract iOS version from runtime key
data['devices'].each do |runtime, devices|
next unless runtime.include?('iOS')
devices.each do |dev|
next unless dev['name'] == device
# Runtime format: "com.apple.CoreSimulator.SimRuntime.iOS-18-1"
match = runtime.match(/iOS[.-](\d+)[.-](\d+)/)
return "#{match[1]}.#{match[2]}" if match
end
end
'unknown'
rescue StandardError
'unknown'
end
public
# App bundle identifier
def bundle_id
'stream.yattee.app'
end
# Yattee Server URL for testing (configurable via env)
def yattee_server_url
ENV.fetch('YATTEE_SERVER_URL', 'https://yp.home.arekf.net')
end
# Extract host from Yattee Server URL for identifier matching
def yattee_server_host
URI.parse(yattee_server_url).host
end
# Yattee Server username for testing (configurable via env)
def yattee_server_username
ENV.fetch('YATTEE_SERVER_USERNAME', nil)
end
# Yattee Server password for testing (configurable via env)
def yattee_server_password
ENV.fetch('YATTEE_SERVER_PASSWORD', nil)
end
# Whether Yattee Server credentials are configured
def yattee_server_credentials?
yattee_server_username && yattee_server_password
end
# Invidious URL for testing (configurable via env)
def invidious_url
ENV.fetch('INVIDIOUS_URL', 'https://invidious.home.arekf.net')
end
# Extract host from Invidious URL for identifier matching
def invidious_host
URI.parse(invidious_url).host
end
# Invidious account email for testing (configurable via env)
def invidious_email
ENV.fetch('INVIDIOUS_EMAIL', nil)
end
# Invidious account password for testing (configurable via env)
def invidious_password
ENV.fetch('INVIDIOUS_PASSWORD', nil)
end
# Whether Invidious credentials are configured
def invidious_credentials?
invidious_email && invidious_password
end
# Invidious URL for an instance fronted by HTTP Basic Auth (configurable via env)
def invidious_basic_auth_url
ENV.fetch('INVIDIOUS_BASIC_AUTH_URL', 'https://i03.s.yattee.stream')
end
# Extract host from the basic-auth Invidious URL
def invidious_basic_auth_host
URI.parse(invidious_basic_auth_url).host
end
# HTTP Basic Auth username for the proxy in front of the Invidious instance
def invidious_basic_auth_username
ENV.fetch('INVIDIOUS_BASIC_AUTH_USERNAME', nil)
end
# HTTP Basic Auth password for the proxy in front of the Invidious instance
def invidious_basic_auth_password
ENV.fetch('INVIDIOUS_BASIC_AUTH_PASSWORD', nil)
end
# Whether the basic-auth proxy credentials for Invidious are configured
def invidious_basic_auth_credentials?
invidious_basic_auth_username && invidious_basic_auth_password
end
# Invidious account email/username behind the basic-auth proxy
def invidious_proxied_email
ENV.fetch('INVIDIOUS_PROXIED_EMAIL', nil)
end
# Invidious account password behind the basic-auth proxy
def invidious_proxied_password
ENV.fetch('INVIDIOUS_PROXIED_PASSWORD', nil)
end
# Whether the proxied Invidious account credentials are configured
def invidious_proxied_credentials?
invidious_proxied_email && invidious_proxied_password
end
# Piped URL for testing (configurable via env)
def piped_url
ENV.fetch('PIPED_URL', 'https://pipedapi.home.arekf.net')
end
# Extract host from Piped URL for identifier matching
def piped_host
URI.parse(piped_url).host
end
# Piped account username for testing (configurable via env)
def piped_username
ENV.fetch('PIPED_USERNAME', nil)
end
# Piped account password for testing (configurable via env)
def piped_password
ENV.fetch('PIPED_PASSWORD', nil)
end
# Whether Piped credentials are configured
def piped_credentials?
piped_username && piped_password
end
# Xcode project path (parent of spec directory)
def project_path
File.expand_path('..', spec_root)
end
# Xcode project file
def xcodeproj
File.join(project_path, 'Yattee.xcodeproj')
end
# Scheme to build
def scheme
'Yattee'
end
# Build configuration
def configuration
'Debug'
end
# Derived data path for builds
def derived_data_path
File.join(project_path, 'build')
end
# Path to built app
def app_path
File.join(derived_data_path, 'Build', 'Products', 'Debug-iphonesimulator', 'Yattee.app')
end
# Spec root directory
def spec_root
File.expand_path('../..', __dir__)
end
# Snapshots directory
def snapshots_root
File.join(spec_root, 'ui_snapshots')
end
# Baseline screenshots directory (device and iOS version specific)
def baseline_dir
File.join(snapshots_root, 'baseline', snapshot_slug)
end
# Current test run screenshots directory (device and iOS version specific)
def current_dir
File.join(snapshots_root, 'current', snapshot_slug)
end
# Diff images directory (device and iOS version specific)
def diff_dir
File.join(snapshots_root, 'diff', snapshot_slug)
end
# False positives YAML file
def false_positives_file
File.join(snapshots_root, 'false_positives.yml')
end
# Default threshold for visual comparison (1% difference allowed)
def default_diff_threshold
0.01
end
# Whether to generate baseline screenshots
def generate_baseline?
ENV['GENERATE_BASELINE'] == '1'
end
# Whether to skip building the app
def skip_build?
ENV['SKIP_BUILD'] == '1'
end
# Whether to keep simulator running after tests
def keep_simulator?
ENV['KEEP_SIMULATOR'] == '1'
end
# Whether to keep app data between runs (skip uninstall)
def keep_app_data?
ENV['KEEP_APP_DATA'] == '1'
end
# Timeout for waiting for elements (seconds)
def element_timeout
10
end
# Time to wait for app to stabilize after launch (seconds)
def app_launch_wait
3
end
# Ensure directories exist
def ensure_directories!
[baseline_dir, current_dir, diff_dir].each do |dir|
FileUtils.mkdir_p(dir)
end
end
end
end
end