diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89a1aff3f..af095afe5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -268,6 +268,70 @@ jobs: run: cargo build --release --locked -p uiharness -p docxy -p xlsxy -p yppxy -p plugin_fixture - name: Sweep runner unit tests run: python -m unittest discover -s scripts -p "test_*.py" -v + # The runner's desktop is 1024x768, and Windows caps a window at the screen + # plus its frame, so every `window-size` wider than that came out 1028x780 + # and the cases saw a narrower ribbon than they asked for (#1779). Give the + # sweep a 1920x1080 screen (the widest case asks for 1400x800 logical px), + # and fail here, with the mode got and the modes offered, if the mode is + # below 1920x1080 or the screen at the runner's scale is below 1400x800 plus + # the frame. The readback is the display mode, not Screen.Bounds or + # GetSystemMetrics, which a DPI-unaware pwsh reports scaled down. + - name: Set the display to 1920x1080 + shell: pwsh + run: | + Add-Type -TypeDefinition @' + using System; + using System.Runtime.InteropServices; + public static class Display { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct DEVMODE { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string dmDeviceName; + public short dmSpecVersion, dmDriverVersion, dmSize, dmDriverExtra; + public int dmFields; + public int dmPositionX, dmPositionY, dmDisplayOrientation, dmDisplayFixedOutput; + public short dmColor, dmDuplex, dmYResolution, dmTTOption, dmCollate; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string dmFormName; + public short dmLogPixels; + public int dmBitsPerPel, dmPelsWidth, dmPelsHeight, dmDisplayFlags, dmDisplayFrequency; + public int dmICMMethod, dmICMIntent, dmMediaType, dmDitherType; + public int dmReserved1, dmReserved2, dmPanningWidth, dmPanningHeight; + } + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + public static extern bool EnumDisplaySettings(string device, int mode, ref DEVMODE dm); + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + public static extern int ChangeDisplaySettings(ref DEVMODE dm, int flags); + [DllImport("user32.dll")] + public static extern bool SetProcessDpiAwarenessContext(IntPtr context); + [DllImport("user32.dll")] + public static extern uint GetDpiForSystem(); + public static DEVMODE Mode(int index) { + var dm = new DEVMODE(); + dm.dmSize = (short)Marshal.SizeOf(typeof(DEVMODE)); + if (!EnumDisplaySettings(null, index, ref dm)) dm.dmPelsWidth = -1; + return dm; + } + } + '@ + $current = -1 # ENUM_CURRENT_SETTINGS + $dm = [Display]::Mode($current) + "before: $($dm.dmPelsWidth)x$($dm.dmPelsHeight)" + $dm.dmPelsWidth = 1920 + $dm.dmPelsHeight = 1080 + $dm.dmFields = 0x80000 -bor 0x100000 # DM_PELSWIDTH | DM_PELSHEIGHT + $code = [Display]::ChangeDisplaySettings([ref]$dm, 0) # this session only + "ChangeDisplaySettings: $code (0 is DISP_CHANGE_SUCCESSFUL)" + $got = [Display]::Mode($current) + $aware = [Display]::SetProcessDpiAwarenessContext([IntPtr]-4) # per-monitor v2, for a true DPI + $dpi = [Display]::GetDpiForSystem() + $logW = [math]::Floor($got.dmPelsWidth * 96 / $dpi) + $logH = [math]::Floor($got.dmPelsHeight * 96 / $dpi) + "after: $($got.dmPelsWidth)x$($got.dmPelsHeight), DPI awareness set: $aware, system DPI $dpi, logical ${logW}x${logH}" + if ($code -ne 0 -or $got.dmPelsWidth -lt 1920 -or $got.dmPelsHeight -lt 1080 -or $logW -lt 1410 -or $logH -lt 815) { + $modes = for ($i = 0; ($m = [Display]::Mode($i)).dmPelsWidth -ge 0; $i++) { "$($m.dmPelsWidth)x$($m.dmPelsHeight)" } + "modes offered: $(($modes | Select-Object -Unique) -join ', ')" + Write-Error "the display is $($got.dmPelsWidth)x$($got.dmPelsHeight) (logical ${logW}x${logH} at $dpi DPI), the sweep needs 1920x1080 and 1410x815 logical (#1779)" + exit 1 + } - name: Sweep timeout-minutes: 90 run: python scripts/ui-linux-sweep.py --timeout 420 --expected uiharness/cases/expected-failures-windows.txt --suite suite/target/release/suite.exe --uiharness target/release/uiharness.exe --run "${{ github.workspace }}/uiharness-runs/sweep" diff --git a/docs/ui-test-harness.md b/docs/ui-test-harness.md index eea4c5d23..b7e102612 100644 --- a/docs/ui-test-harness.md +++ b/docs/ui-test-harness.md @@ -2112,6 +2112,18 @@ script (Windows runs them two to three times slower), and uploads `ui-sweep-windows` whatever the result. Like the Linux job it fails the PR on a new failure or an unexpected pass (#1544). +Windows caps a window at the screen plus its frame, so the Windows sweep needs +a screen at least as large as the widest `window-size` a case asks for +(1400x800 logical pixels): on the runner's 1024x768 desktop every such window +came out 1028x780 and the ribbon cases saw narrower ribbons than they asked +for (#1779); the same clamp broke the Project table and Timeline, page line +number and style gallery cases (#1780, #1781, #1782). The job sets the +display to 1920x1080 before the sweep and fails there, naming the mode it got +and the modes offered, if the mode is smaller or the screen at the runner's +scale (its size in logical pixels) is below 1410x815, the widest window plus +its frame. The `window-size` reply echoes the request, not the size the window +got. + ### Capture on macOS Nothing outside the app can photograph a macOS harness window: it is never on diff --git a/uiharness/cases/expected-failures-windows.txt b/uiharness/cases/expected-failures-windows.txt index da8e24441..743fb0d4b 100644 --- a/uiharness/cases/expected-failures-windows.txt +++ b/uiharness/cases/expected-failures-windows.txt @@ -15,24 +15,6 @@ # vbottom_inset at 200% is 323, the case expects 241 (as on Linux). doc-rulers.uit | ruler positions follow the document geometry at 100 and 200 percent | #808 -# Ribbon groups clipped or collapsed differently at the Windows CI widths. -ribbon-fit.uit | sheet ribbon: no tab is clipped vertically | #1779 -ribbon-fit.uit | widening restores full groups | #1779 -sheet-ribbon.uit | sheet Home ribbon: no group is clipped vertically | #1779 -project-plugin-ui.uit | the Task tab with a contributed group collapses whole groups, never a label | #1779 - -# Project table and Timeline geometry at the Windows window sizes. -project-selection.uit | project the default window shows the whole Entry table and a narrowed pane does not | #1780 -project-timeline-box.uit | project dragging the box's right edge out widens the span and narrows the days | #1780 -project-timeline-grip.uit | project the grip stops at half the window and follows the window's height | #1780 -project-timeline-view.uit | project timeline highlights the chart's visible range and follows its scroll | #1780 - -# Line number positions in print layout. -page-line-numbers.uit | line numbers draw in print layout, continue across pages and columns, and follow the ribbon toggle | #1781 - -# `shot gallery` cannot be captured (ERROR step). -style-gallery.uit | style gallery tiles in dark and light | #1782 | error - # Border colour asserts that fail in some runs only. gantt-scroll.uit | gantt panes stay aligned while scrolling | #1783 | flaky project-cells.uit | project unchanged edits preserve redo and cell border follows navigation | #1783 | flaky