From 527762fd36fdc8642953786d692795677bcf0a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 8 May 2026 21:13:34 -0400 Subject: [PATCH] Harden PRD live site smoke coverage --- crates/ely_app/src/services/mod.rs | 3 + crates/ely_app/src/services/prd_live_sites.rs | 112 ++++++++++++++++++ .../src/services/servo_sidecar_tests.rs | 87 ++------------ crates/ely_app/src/shell/web_surface.rs | 4 + .../src/shell/web_surface_live_site_tests.rs | 83 +++++++++++++ crates/ely_servo_host/tests/sidecar.rs | 40 ++++++- .../ely_servo_host/tests/sidecar/support.rs | 2 +- 7 files changed, 254 insertions(+), 77 deletions(-) create mode 100644 crates/ely_app/src/services/prd_live_sites.rs create mode 100644 crates/ely_app/src/shell/web_surface_live_site_tests.rs diff --git a/crates/ely_app/src/services/mod.rs b/crates/ely_app/src/services/mod.rs index 5565620..31d5a97 100644 --- a/crates/ely_app/src/services/mod.rs +++ b/crates/ely_app/src/services/mod.rs @@ -5,5 +5,8 @@ pub mod plugin_packages; pub mod plugin_signatures; pub mod servo_sidecar; +#[cfg(all(test, feature = "live-site-smoke"))] +pub(crate) mod prd_live_sites; + #[cfg(test)] mod plugin_package_test_support; diff --git a/crates/ely_app/src/services/prd_live_sites.rs b/crates/ely_app/src/services/prd_live_sites.rs new file mode 100644 index 0000000..0d52622 --- /dev/null +++ b/crates/ely_app/src/services/prd_live_sites.rs @@ -0,0 +1,112 @@ +use std::{collections::BTreeSet, error::Error, fs, path::PathBuf}; + +pub(crate) const PRD_TOP_SITE_CASES: &[LiveSiteCase] = &[ + LiveSiteCase { url: "https://github.com", title_fragment: "GitHub" }, + LiveSiteCase { url: "https://example.com", title_fragment: "Example Domain" }, + LiveSiteCase { url: "https://servo.org/", title_fragment: "Servo" }, +]; + +pub(crate) const PRD_REFERENCE_SITE_CASES: &[LiveSiteCase] = &[ + LiveSiteCase { + url: "https://blog.google/products-and-platforms/products/chrome/new-chrome-productivity-features/", + title_fragment: "Chrome", + }, + LiveSiteCase { + url: "https://www.microsoft.com/en-us/edge/features/vertical-tabs", + title_fragment: "Microsoft Edge", + }, + LiveSiteCase { + url: "https://resources.arc.net/hc/en-us/articles/19230755904151-Favorites-Top-Tabs-Across-Every-Space", + title_fragment: "Favorites", + }, + LiveSiteCase { + url: "https://resources.arc.net/hc/en-us/articles/19228855311127-Auto-Archive-Clean-as-you-go", + title_fragment: "Auto Archive", + }, + LiveSiteCase { url: "https://vivaldi.com/features/workspaces/", title_fragment: "Workspaces" }, + LiveSiteCase { + url: "https://help.vivaldi.com/desktop/tabs/tab-tiling/", + title_fragment: "Tab Tiling", + }, + LiveSiteCase { url: "https://www.gpui.rs/", title_fragment: "gpui" }, + LiveSiteCase { url: "https://docs.rs/gpui", title_fragment: "gpui" }, + LiveSiteCase { url: "https://zed.dev/blog/videogame", title_fragment: "Leveraging Rust" }, + LiveSiteCase { + url: "https://github.com/longbridge/gpui-component/", + title_fragment: "gpui-component", + }, + LiveSiteCase { + url: "https://github.com/zed-industries/awesome-gpui/", + title_fragment: "awesome-gpui", + }, + LiveSiteCase { url: "https://servo.org/", title_fragment: "Servo" }, + LiveSiteCase { + url: "https://servo.org/blog/2026/04/13/servo-0.1.0-release/", + title_fragment: "Servo", + }, + LiveSiteCase { url: "https://developers.cloudflare.com/d1/", title_fragment: "Cloudflare" }, + LiveSiteCase { + url: "https://developers.cloudflare.com/workers/platform/storage-options/", + title_fragment: "Cloudflare", + }, + LiveSiteCase { + url: "https://developers.cloudflare.com/kv/concepts/how-kv-works/", + title_fragment: "Cloudflare", + }, + LiveSiteCase { url: "https://better-auth.com/blog/1-5", title_fragment: "Better Auth" }, + LiveSiteCase { + url: "https://developers.cloudflare.com/d1/platform/limits/", + title_fragment: "Cloudflare", + }, + LiveSiteCase { + url: "https://component-model.bytecodealliance.org/", + title_fragment: "WebAssembly Component Model", + }, + LiveSiteCase { + url: "https://docs.wasmtime.dev/api/wasmtime/component/index.html", + title_fragment: "wasmtime", + }, + LiveSiteCase { url: "https://docs.wasmtime.dev/security.html", title_fragment: "Wasmtime" }, +]; + +pub(crate) struct LiveSiteCase { + pub(crate) url: &'static str, + pub(crate) title_fragment: &'static str, +} + +pub(crate) fn assert_prd_reference_urls_are_covered() -> Result<(), Box> { + let prd = fs::read_to_string(prd_path())?; + let prd_urls = prd_reference_urls(&prd); + let covered_urls = PRD_REFERENCE_SITE_CASES + .iter() + .map(|case| normalized_url(case.url)) + .collect::>(); + let missing_urls = prd_urls + .iter() + .filter(|url| !covered_urls.contains(url.as_str())) + .cloned() + .collect::>(); + + assert!(missing_urls.is_empty(), "missing PRD live-site smoke cases: {missing_urls:?}"); + assert_eq!(prd_urls.len(), PRD_REFERENCE_SITE_CASES.len()); + Ok(()) +} + +fn prd_path() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..").join("..").join("PRD.md") +} + +fn prd_reference_urls(prd: &str) -> Vec { + prd.lines() + .filter(|line| line.starts_with("[R")) + .filter_map(|line| { + let start = line.find("https://")?; + let url = line[start..].split_whitespace().next()?; + Some(normalized_url(url)) + }) + .collect() +} + +fn normalized_url(url: &str) -> String { + url.trim().trim_end_matches('/').to_string() +} diff --git a/crates/ely_app/src/services/servo_sidecar_tests.rs b/crates/ely_app/src/services/servo_sidecar_tests.rs index c8ee202..b8ac8e1 100644 --- a/crates/ely_app/src/services/servo_sidecar_tests.rs +++ b/crates/ely_app/src/services/servo_sidecar_tests.rs @@ -2,87 +2,18 @@ use std::error::Error; use super::*; +#[cfg(feature = "live-site-smoke")] +use crate::services::prd_live_sites::{ + LiveSiteCase, PRD_REFERENCE_SITE_CASES, PRD_TOP_SITE_CASES, + assert_prd_reference_urls_are_covered, +}; + #[cfg(feature = "live-site-smoke")] const LIVE_SITE_WIDTH: u32 = 934; #[cfg(feature = "live-site-smoke")] const LIVE_SITE_HEIGHT: u32 = 657; #[cfg(feature = "live-site-smoke")] const MINIMUM_CONTENT_PIXELS: u64 = 1_000; -#[cfg(feature = "live-site-smoke")] -const PRD_TOP_SITE_CASES: &[LiveSiteCase] = &[ - LiveSiteCase { url: "https://github.com", title_fragment: "GitHub" }, - LiveSiteCase { url: "https://example.com", title_fragment: "Example Domain" }, - LiveSiteCase { url: "https://servo.org/", title_fragment: "Servo" }, -]; -#[cfg(feature = "live-site-smoke")] -const PRD_REFERENCE_SITE_CASES: &[LiveSiteCase] = &[ - LiveSiteCase { - url: "https://blog.google/products-and-platforms/products/chrome/new-chrome-productivity-features/", - title_fragment: "Chrome", - }, - LiveSiteCase { - url: "https://www.microsoft.com/en-us/edge/features/vertical-tabs", - title_fragment: "Microsoft Edge", - }, - LiveSiteCase { - url: "https://resources.arc.net/hc/en-us/articles/19230755904151-Favorites-Top-Tabs-Across-Every-Space", - title_fragment: "Favorites", - }, - LiveSiteCase { - url: "https://resources.arc.net/hc/en-us/articles/19228855311127-Auto-Archive-Clean-as-you-go", - title_fragment: "Auto Archive", - }, - LiveSiteCase { url: "https://vivaldi.com/features/workspaces/", title_fragment: "Workspaces" }, - LiveSiteCase { - url: "https://help.vivaldi.com/desktop/tabs/tab-tiling/", - title_fragment: "Tab Tiling", - }, - LiveSiteCase { url: "https://www.gpui.rs/", title_fragment: "gpui" }, - LiveSiteCase { url: "https://docs.rs/gpui/latest/gpui/", title_fragment: "gpui" }, - LiveSiteCase { url: "https://zed.dev/blog/videogame", title_fragment: "Leveraging Rust" }, - LiveSiteCase { - url: "https://github.com/longbridge/gpui-component/", - title_fragment: "gpui-component", - }, - LiveSiteCase { - url: "https://github.com/zed-industries/awesome-gpui/", - title_fragment: "awesome-gpui", - }, - LiveSiteCase { url: "https://servo.org/", title_fragment: "Servo" }, - LiveSiteCase { - url: "https://servo.org/blog/2026/04/13/servo-0.1.0-release/", - title_fragment: "Servo", - }, - LiveSiteCase { url: "https://developers.cloudflare.com/d1/", title_fragment: "Cloudflare" }, - LiveSiteCase { - url: "https://developers.cloudflare.com/workers/platform/storage-options/", - title_fragment: "Cloudflare", - }, - LiveSiteCase { - url: "https://developers.cloudflare.com/kv/concepts/how-kv-works/", - title_fragment: "Cloudflare", - }, - LiveSiteCase { url: "https://better-auth.com/blog/1-5", title_fragment: "Better Auth" }, - LiveSiteCase { - url: "https://developers.cloudflare.com/d1/platform/limits/", - title_fragment: "Cloudflare", - }, - LiveSiteCase { - url: "https://component-model.bytecodealliance.org/", - title_fragment: "WebAssembly Component Model", - }, - LiveSiteCase { - url: "https://docs.wasmtime.dev/api/wasmtime/component/index.html", - title_fragment: "wasmtime", - }, - LiveSiteCase { url: "https://docs.wasmtime.dev/security.html", title_fragment: "Wasmtime" }, -]; - -#[cfg(feature = "live-site-smoke")] -struct LiveSiteCase { - url: &'static str, - title_fragment: &'static str, -} #[test] fn accepts_loading_report_with_visible_content() -> Result<(), ServoSidecarError> { @@ -137,6 +68,12 @@ fn desktop_sidecar_opens_prd_reference_sites() -> Result<(), Box> { assert_live_sites_render(PRD_REFERENCE_SITE_CASES) } +#[cfg(feature = "live-site-smoke")] +#[test] +fn prd_reference_live_site_cases_cover_prd_urls() -> Result<(), Box> { + assert_prd_reference_urls_are_covered() +} + #[cfg(feature = "live-site-smoke")] fn assert_live_sites_render(cases: &[LiveSiteCase]) -> Result<(), Box> { let client = ServoSidecarClient::new()?; diff --git a/crates/ely_app/src/shell/web_surface.rs b/crates/ely_app/src/shell/web_surface.rs index cbc4eb2..117b4a0 100644 --- a/crates/ely_app/src/shell/web_surface.rs +++ b/crates/ely_app/src/shell/web_surface.rs @@ -440,3 +440,7 @@ mod tests { )) } } + +#[cfg(all(test, feature = "live-site-smoke"))] +#[path = "web_surface_live_site_tests.rs"] +mod web_surface_live_site_tests; diff --git a/crates/ely_app/src/shell/web_surface_live_site_tests.rs b/crates/ely_app/src/shell/web_surface_live_site_tests.rs new file mode 100644 index 0000000..dd026d9 --- /dev/null +++ b/crates/ely_app/src/shell/web_surface_live_site_tests.rs @@ -0,0 +1,83 @@ +use std::error::Error; + +use ely_domain::{BrowserTab, ProfileId, SpaceId, TabId, UrlText}; +use gpui::{Bounds, point, px, size}; + +use crate::{ + services::prd_live_sites::{ + LiveSiteCase, PRD_REFERENCE_SITE_CASES, PRD_TOP_SITE_CASES, + assert_prd_reference_urls_are_covered, + }, + shell::{ + web_surface_frame::WebSurfaceFrame, + web_surface_geometry::{WebSurfaceScrollOffset, WebSurfaceSize}, + }, +}; + +use super::WebSurfaceStore; + +const LIVE_SURFACE_WIDTH: u32 = 934; +const LIVE_SURFACE_HEIGHT: u32 = 657; + +#[test] +fn web_surface_cases_cover_prd_reference_urls() -> Result<(), Box> { + assert_prd_reference_urls_are_covered() +} + +#[test] +fn web_surface_opens_and_renders_prd_top_sites() -> Result<(), Box> { + assert_web_surfaces_render(PRD_TOP_SITE_CASES) +} + +#[test] +fn web_surface_opens_and_renders_prd_reference_sites() -> Result<(), Box> { + assert_web_surfaces_render(PRD_REFERENCE_SITE_CASES) +} + +fn assert_web_surfaces_render(cases: &[LiveSiteCase]) -> Result<(), Box> { + let mut store = WebSurfaceStore::new(); + for case in cases { + let tab = web_tab(case.url)?; + let bounds = live_surface_bounds(); + + assert!(store.record_viewport_size(tab.id(), bounds), "{}", case.url); + let request = store + .prepare_request(&tab) + .ok_or_else(|| format!("missing web surface request for {}", case.url))?; + let snapshot = request.client.snapshot(request.snapshot_request)?; + let frame = WebSurfaceFrame::from_snapshot( + request.requested_url, + request.scroll_offset, + request.click_point, + request.typed_text, + snapshot, + )?; + + assert_eq!( + frame.size(), + WebSurfaceSize { width: LIVE_SURFACE_WIDTH, height: LIVE_SURFACE_HEIGHT }, + "{}", + case.url + ); + assert_eq!(frame.scroll_offset(), WebSurfaceScrollOffset::default(), "{}", case.url); + assert!(frame.url_label().contains(normalized_url(case.url)), "{}", frame.url_label()); + assert!(frame.title_label().contains(case.title_fragment), "{}", frame.title_label()); + assert_eq!(frame.detail_label(), "934x657", "{}", case.url); + } + Ok(()) +} + +fn live_surface_bounds() -> Bounds { + Bounds::new( + point(px(0.0), px(0.0)), + size(px(LIVE_SURFACE_WIDTH as f32), px(LIVE_SURFACE_HEIGHT as f32)), + ) +} + +fn web_tab(url: &str) -> Result> { + Ok(BrowserTab::new(TabId::new(), SpaceId::new(), ProfileId::new(), "Web", UrlText::parse(url)?)) +} + +fn normalized_url(url: &str) -> &str { + url.trim_end_matches('/') +} diff --git a/crates/ely_servo_host/tests/sidecar.rs b/crates/ely_servo_host/tests/sidecar.rs index 8f67bcb..30f8661 100644 --- a/crates/ely_servo_host/tests/sidecar.rs +++ b/crates/ely_servo_host/tests/sidecar.rs @@ -1,12 +1,31 @@ #![cfg(feature = "servo-engine")] -use std::error::Error; +use std::{collections::BTreeSet, error::Error, fs, path::PathBuf}; #[path = "sidecar/support.rs"] mod support; use support::*; +#[test] +fn sidecar_prd_reference_cases_cover_prd_urls() -> Result<(), Box> { + let prd = fs::read_to_string(prd_path())?; + let prd_urls = prd_reference_urls(&prd); + let covered_urls = PRD_REFERENCE_SITE_COMPATIBILITY_CASES + .iter() + .map(|case| normalized_url(case.url)) + .collect::>(); + let missing_urls = prd_urls + .iter() + .filter(|url| !covered_urls.contains(url.as_str())) + .cloned() + .collect::>(); + + assert!(missing_urls.is_empty(), "missing PRD sidecar smoke cases: {missing_urls:?}"); + assert_eq!(prd_urls.len(), PRD_REFERENCE_SITE_COMPATIBILITY_CASES.len()); + Ok(()) +} + #[test] fn sidecar_opens_and_renders_prd_sites_to_rgba_files() -> Result<(), Box> { for case in PRD_SITE_COMPATIBILITY_CASES { @@ -18,6 +37,25 @@ fn sidecar_opens_and_renders_prd_sites_to_rgba_files() -> Result<(), Box PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..").join("..").join("PRD.md") +} + +fn prd_reference_urls(prd: &str) -> Vec { + prd.lines() + .filter(|line| line.starts_with("[R")) + .filter_map(|line| { + let start = line.find("https://")?; + let url = line[start..].split_whitespace().next()?; + Some(normalized_url(url)) + }) + .collect() +} + +fn normalized_url(url: &str) -> String { + url.trim().trim_end_matches('/').to_string() +} + #[test] fn sidecar_opens_and_renders_prd_reference_sites_to_rgba_files() -> Result<(), Box> { for case in PRD_REFERENCE_SITE_COMPATIBILITY_CASES { diff --git a/crates/ely_servo_host/tests/sidecar/support.rs b/crates/ely_servo_host/tests/sidecar/support.rs index f27c084..18e48ee 100644 --- a/crates/ely_servo_host/tests/sidecar/support.rs +++ b/crates/ely_servo_host/tests/sidecar/support.rs @@ -45,7 +45,7 @@ pub(super) const PRD_REFERENCE_SITE_COMPATIBILITY_CASES: &[PrdSiteCompatibilityC title_fragment: "Tab Tiling", }, PrdSiteCompatibilityCase { url: "https://www.gpui.rs/", title_fragment: "gpui" }, - PrdSiteCompatibilityCase { url: "https://docs.rs/gpui/latest/gpui/", title_fragment: "gpui" }, + PrdSiteCompatibilityCase { url: "https://docs.rs/gpui", title_fragment: "gpui" }, PrdSiteCompatibilityCase { url: "https://zed.dev/blog/videogame", title_fragment: "Leveraging Rust",