diff --git a/.github/workflows/desktop-e2e-hosted.yml b/.github/workflows/desktop-e2e-hosted.yml index 4d807c53..a9223486 100644 --- a/.github/workflows/desktop-e2e-hosted.yml +++ b/.github/workflows/desktop-e2e-hosted.yml @@ -11,7 +11,7 @@ on: description: 'Test depth: Quick, Full with exit/restart, or 30-minute Soak' type: choice options: [Quick, Full, Soak] - default: Quick + default: Full seed: description: 'Soak random seed; leave empty to generate one' type: string @@ -40,11 +40,11 @@ concurrency: jobs: desktop: - name: Desktop E2E (${{ inputs.profile || (github.event_name == 'schedule' && 'Soak') || 'Quick' }}) + name: Desktop E2E (${{ inputs.profile || (github.event_name == 'schedule' && 'Soak') || 'Full' }}) runs-on: windows-2022 timeout-minutes: ${{ (inputs.profile == 'Soak' || github.event_name == 'schedule') && 50 || 15 }} env: - E2E_PROFILE: ${{ inputs.profile || (github.event_name == 'schedule' && 'Soak') || 'Quick' }} + E2E_PROFILE: ${{ inputs.profile || (github.event_name == 'schedule' && 'Soak') || 'Full' }} steps: - uses: actions/checkout@v6 with: diff --git a/docs/desktop-e2e.md b/docs/desktop-e2e.md index f931173e..9757aaea 100644 --- a/docs/desktop-e2e.md +++ b/docs/desktop-e2e.md @@ -231,7 +231,7 @@ Median, p95 and maximum are diagnostic observations, not compositor presentation latency or a portable speed benchmark. A switch has a 2-second correctness timeout. Normal CI compiles this harness with `-BuildOnly`. Actual input tests require -an unlocked interactive Windows desktop. The hosted Quick workflow checks this +an unlocked interactive Windows desktop. The hosted workflow checks this on GitHub's Windows VM; unusable hosted/service sessions fail rather than skip or silently report success. The deterministic navigation and modifier snapshot regressions also run in the regular Architecture suite. @@ -240,10 +240,12 @@ snapshot regressions also run in the regular Architecture suite. `.github/workflows/desktop-e2e-hosted.yml` provides **Desktop E2E Hosted** on GitHub's `windows-2022` virtual machine, without a local runner. Every pull -request runs Quick as the stable **Desktop E2E (Quick)** check. No path filters +request runs Full as the stable **Desktop E2E (Full)** check, the same profile +the release runs, so a defect that only appears after restart fails the pull +request rather than the release; it adds about a minute over Quick. No path filters skip this check. Soak runs every day at 18:00 UTC on the default branch, which also catches changes in the hosted Windows image, and manual dispatch offers -Quick, Full or Soak. These runs build their own Release binary. The release +Quick, Full (default) or Soak. These runs build their own Release binary. The release workflow calls the same workflow with Full and `executable-artifact`, so it tests the very `WindowTabs.exe` the draft will carry; the draft is created only after the regression suites, the smoke test, the version check and that run all pass. diff --git a/docs/testing.md b/docs/testing.md index c74a3188..91900d14 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -101,9 +101,9 @@ The separate [desktop E2E suite](desktop-e2e.md) exercises the actual Release entry point, real mouse drag grouping, frequent foreground switching, maximized tabs, closing the active window, tray-menu exit and restart. It requires an idle interactive desktop and is not part of the focus-safe default suite. -GitHub's separate **Desktop E2E Hosted** workflow runs Quick on pull requests -using an isolated `windows-2022` VM; manual Full verifies exit/restart and 1,320 -switches. Both profiles have passed on hosted runners. See the linked E2E guide +GitHub's separate **Desktop E2E Hosted** workflow runs Full on pull requests +using an isolated `windows-2022` VM, verifying exit/restart and 1,320 switches +like the release does; Quick remains a manual choice. See the linked E2E guide for run evidence, activation requirements and remaining limitations. | Risk | Existing evidence | Next validation | diff --git a/tests/DesktopE2E.cs b/tests/DesktopE2E.cs index a84d43db..81b16a08 100644 --- a/tests/DesktopE2E.cs +++ b/tests/DesktopE2E.cs @@ -26,6 +26,8 @@ static partial class DesktopE2E [DllImport("user32.dll")] static extern bool GetGUIThreadInfo(uint thread, ref GuiThread info); [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr hwnd); [DllImport("user32.dll")] static extern bool ShowWindow(IntPtr hwnd, int command); + [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern IntPtr GetProp(IntPtr hwnd, string name); + [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern IntPtr RemoveProp(IntPtr hwnd, string name); [DllImport("user32.dll")] static extern bool IsWindow(IntPtr hwnd); [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr hwnd, uint command); [DllImport("user32.dll")] static extern int GetWindowLong(IntPtr hwnd, int index); @@ -512,9 +514,17 @@ static async Task Run(int switches, int cycles, int durationMinutes) var saved=Json.Deserialize>(File.ReadAllText(Path.Combine(Root,"WindowTabsSettings.json"))); Check(saved.ContainsKey("version") && !string.IsNullOrEmpty((string)saved["version"]),"Startup did not persist its settings version"); Check((string)saved["alignment"]=="Left" && !(bool)saved["enableTabbingByDefault"],"Fixture settings were lost"); + Check(saved.ContainsKey("rememberedGroups") && Json.Serialize(saved["rememberedGroups"]).Contains("WindowTabs E2E "),"Exit did not remember the dragged group"); Check(!HasCrash(),"Application logged an exception during the cycle"); completed++; Log("PASS: cycle "+completed+"; original entry point, drag grouping, switching, close and normal exit"); // The next cycle reuses the saved portable configuration and reacquires the real singleton. + // Forget the dragged group, in the settings and on the windows themselves, so the + // surviving helpers start apart and are dragged together again. + var survivors=Forms.Where(f=>!f.IsDisposed).ToList(); + Check(survivors.All(f=>GetProp(f.Handle,"WindowTabs.Group.v1")!=IntPtr.Zero),"Exit did not keep the group on its windows"); + foreach(var f in survivors) RemoveProp(f.Handle,"WindowTabs.Group.v1"); + saved.Remove("rememberedGroups"); + File.WriteAllText(Path.Combine(Root,"WindowTabsSettings.json"),Json.Serialize(saved)); } } [STAThread] static int Main(string[] args)