mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 06:00:33 +02:00
tests/fakeframe: a container that stands in for the Frame (Arch Linux, or Arch Linux ARM on arm64) with sshd, rsync, Valve's steamos-devkit-service and hooks (vendored unmodified), a fake Steam client for the devkit pipe and the DevTools port (the app's JavaScript runs in Node against stand-in SteamClient/appStore objects), stubs for steam, wpctl, flatpak, podman, Lepton and friends, battery and thermal files under /sys, and fault switches (fakeframe-ctl): pairing mode, approve/deny/timeout, Steam not running, headset asleep, sshd off, disk full, runtimes missing. A second container is the computer running Frame Control. tests/e2e: 29 tests driving the real ui/server.py, frame_connect.py and frame_titles.py against it; skipped unless FRAME_E2E=1. scripts/e2e.sh builds, runs and tears down; CI runs it on ubuntu-24.04-arm. tests/smoke + scripts/frame-smoke.sh: the core cases against a real Frame, recorded with its BUILD_ID, cleaning up after itself; --pair to pair a throwaway key. docs/testing.md describes the layers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
87 lines
4.5 KiB
Python
87 lines
4.5 KiB
Python
"""Status, Steam library, volume, clipboard, Flatpaks and the desktop capture,
|
|
through the server against the fake Frame."""
|
|
import unittest
|
|
|
|
import harness
|
|
from harness import api, ctl, launches, ok, state, wait_for
|
|
|
|
harness.require()
|
|
|
|
|
|
class Device(harness.FrameTestCase):
|
|
def test_status(self):
|
|
s = ok('GET', '/api/status')
|
|
self.assertEqual(s['hostname'], 'frame')
|
|
self.assertEqual(s['os'], {'version': '0.3.0', 'build': '20260922.6101926', 'variant': 'vr'})
|
|
b = s['battery']
|
|
self.assertEqual((b['percent'], b['status'], b['health']), (76, 'Charging', 'Good'))
|
|
self.assertAlmostEqual(b['watts'], 9.62, places=1)
|
|
self.assertAlmostEqual(b['tempC'], 31.2, places=3)
|
|
self.assertEqual(s['power'], {'type': 'C PD [PD_PPS]', 'watts': 20.0})
|
|
self.assertEqual(s['temp'], 41.5)
|
|
self.assertEqual(s['wifi'], {'ssid': 'Fake:Frame Wi-Fi', 'signal': 72})
|
|
self.assertEqual(s['volume'], {'level': 0.4, 'muted': False})
|
|
self.assertEqual(s['services'], {'steamvr': True, 'desktop': True, 'lepton': False, 'rdp': False})
|
|
# Runtimes and Lepton are Steam "apps" too; the status hides them.
|
|
self.assertEqual([g['name'] for g in s['games']], ['Beat Saber'])
|
|
self.assertIsNotNone(s['disk']['home'])
|
|
|
|
ctl('battery', 'capacity=15', 'status=Discharging', 'current_now=-900000')
|
|
b = ok('GET', '/api/status')['battery']
|
|
self.assertEqual((b['percent'], b['status']), (15, 'Discharging'))
|
|
self.assertLess(b['watts'], 0)
|
|
|
|
def test_volume_and_mute(self):
|
|
ok('POST', '/api/volume', {'level': 0.55, 'muted': True})
|
|
self.assertEqual(state()['volume'], {'level': 0.55, 'muted': True})
|
|
self.assertEqual(ok('GET', '/api/status')['volume'], {'level': 0.55, 'muted': True})
|
|
status, out, _ = api('POST', '/api/volume', {'level': 2})
|
|
self.assertEqual(status, 400, out)
|
|
|
|
def test_clipboard_goes_to_klipper(self):
|
|
text = 'héllo from the e2e tests\nline two, with a trailing newline\n'
|
|
out = ok('POST', '/api/clipboard', {'text': text})
|
|
# ${#text} counts bytes or characters depending on the session's locale.
|
|
self.assertRegex(out['message'], r'^copied via Klipper \(\d+ chars\)$')
|
|
self.assertEqual(state()['clipboard'], [text])
|
|
|
|
def test_flatpak_install_and_remove(self):
|
|
ok('POST', '/api/flatpak', {'id': 'org.videolan.VLC', 'action': 'install'})
|
|
self.assertEqual([f['id'] for f in ok('GET', '/api/status')['flatpaks']], ['org.videolan.VLC'])
|
|
ok('POST', '/api/flatpak', {'id': 'org.videolan.VLC', 'action': 'uninstall'})
|
|
self.assertEqual(state()['flatpaks'], [])
|
|
status, out, _ = api('POST', '/api/flatpak', {'id': 'org.example.missing', 'action': 'install'})
|
|
self.assertEqual(status, 502)
|
|
self.assertIn('Nothing matches org.example.missing', out['error'])
|
|
|
|
def test_desktop_capture(self):
|
|
status, png, headers = api('GET', '/api/screenshot')
|
|
self.assertEqual(status, 200, png)
|
|
self.assertTrue(png.startswith(b'\x89PNG\r\n\x1a\n'))
|
|
self.assertEqual(headers.get('X-Capture-Source'), 'gamescope')
|
|
|
|
def test_steam_library_and_installs(self):
|
|
owned = ok('GET', '/api/steam/owned')
|
|
self.assertEqual(owned['country'], 'AU')
|
|
games = {g['id']: g for g in owned['games']}
|
|
self.assertEqual(set(games), {2379780, 274190, 620980})
|
|
self.assertEqual((games[2379780]['frame'], games[620980]['installed']), (3, True))
|
|
# Balatro queues at once; Broforce stops at the options dialog, which
|
|
# frame_steam.py accepts with ContinueInstall().
|
|
for appid, name in ((2379780, 'Balatro'), (274190, 'Broforce')):
|
|
out = ok('POST', '/api/steam', {'appid': appid, 'action': 'install'})
|
|
self.assertEqual(out, {'state': 'downloading', 'message': f'{name} is queued to download on the Frame'})
|
|
self.assertEqual(ok('GET', '/api/steam/owned')['download']['appid'], 274190)
|
|
|
|
def test_launch_and_store_page(self):
|
|
ok('POST', '/api/launch', {'appid': 620980})
|
|
run = wait_for(lambda: launches('rungameid'), 10, 'the launch to reach Steam')[-1]
|
|
self.assertEqual((run['appid'], run['started']), (620980, True))
|
|
ok('POST', '/api/steam', {'appid': 1145360, 'action': 'store'})
|
|
wait_for(lambda: state()['steam']['pages'], 10, 'the store page')
|
|
self.assertEqual(state()['steam']['pages'][0]['title'], 'Hades on Steam')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|