b22934fe0d
Browser SD files are persisted in OPFS (web/sdstore.js) and edited in the Files tab. At boot the browser builds a deterministic 64 MiB FAT32 image (web/fat32.js) with nested directories and VFAT long names, then mounts it as a raw block device, bypassing QEMU's broken populated-vvfat WASM coroutine path. Guest writes mutate only the ephemeral image. Exposes a hidden #frame-generation indicator that increments on every e-ink flush, so automation can wait on real display updates instead of fixed sleeps. Adds tests/ with a Makefile runner: 'make test' for JS + FAT32/mtools checks, 'make browser-test' for a headless-Firefox boot of the official CrossPoint 1.4.1 firmware that navigates to Test/example.txt. Bumps third_party/qemu to the SPI-SD/CMD13 fixes and updates README/ROADMAP/ISSUES for the new architecture.
78 lines
2.8 KiB
Python
78 lines
2.8 KiB
Python
from pathlib import Path
|
|
import time
|
|
from selenium import webdriver
|
|
from selenium.webdriver.common.action_chains import ActionChains
|
|
from selenium.webdriver.common.by import By
|
|
from selenium.webdriver.firefox.options import Options
|
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
options = Options()
|
|
options.add_argument('-headless')
|
|
driver = webdriver.Firefox(options=options)
|
|
driver.set_page_load_timeout(60)
|
|
try:
|
|
driver.get('http://127.0.0.1:8180/web/?firmware=/_scratch/crosspoint-reader-1.4.1-release-firmware.bin&variant=x3')
|
|
confirm = driver.find_element(By.CSS_SELECTOR, '[data-button="confirm"]')
|
|
WebDriverWait(driver, 8 * 60).until(lambda _: confirm.get_attribute('disabled') is None)
|
|
|
|
indicator = driver.find_element(By.ID, 'frame-generation')
|
|
|
|
def generation():
|
|
return int(indicator.get_attribute('value') or '-1')
|
|
|
|
def wait_frame(previous, timeout=180):
|
|
WebDriverWait(driver, timeout).until(lambda _: generation() > previous)
|
|
|
|
def wait_stable(quiet=5, timeout=180):
|
|
deadline = time.time() + timeout
|
|
last = generation()
|
|
stable_since = time.time()
|
|
while time.time() < deadline:
|
|
time.sleep(0.25)
|
|
current = generation()
|
|
if current != last:
|
|
last = current
|
|
stable_since = time.time()
|
|
elif time.time() - stable_since >= quiet:
|
|
return current
|
|
raise RuntimeError('display did not settle')
|
|
|
|
power = driver.find_element(By.CSS_SELECTOR, '[data-button="power"]')
|
|
before = generation()
|
|
ActionChains(driver).click_and_hold(power).perform()
|
|
wait_frame(before)
|
|
ActionChains(driver).release(power).perform()
|
|
wait_stable()
|
|
|
|
def press(button):
|
|
driver.execute_script('arguments[0].scrollIntoView({block: "center"})', button)
|
|
before = wait_stable()
|
|
ActionChains(driver).click_and_hold(button).perform()
|
|
time.sleep(0.3)
|
|
ActionChains(driver).release(button).perform()
|
|
wait_frame(before)
|
|
wait_stable()
|
|
|
|
press(confirm) # Browse Files
|
|
press(confirm) # Test
|
|
press(confirm) # example.txt
|
|
time.sleep(60)
|
|
|
|
status = driver.find_element(By.ID, 'status').text
|
|
log = driver.find_element(By.ID, 'log').text
|
|
(ROOT / '_scratch/browser-raw-sd.log').write_text(log)
|
|
driver.save_screenshot(str(ROOT / '_scratch/browser-raw-sd.png'))
|
|
|
|
bad = (
|
|
'RuntimeError', 'Guru Meditation', "panic'ed", 'Invalid SD card size',
|
|
'FS error', 'SPI:', 'SD:', 'Unexpected response to cmd',
|
|
)
|
|
matched = [marker for marker in bad if marker in log or marker in status]
|
|
if matched:
|
|
raise RuntimeError(f'emulator failure detected: {matched}')
|
|
print(f'browser raw-SD test passed at frame generation {generation()}')
|
|
finally:
|
|
driver.quit()
|