Skip to content

Commit 0dd7225

Browse files
committed
ci(ui-tests): take a screenshot when canceled
Sometimes the logs are empty and it is highly unclear what has happened. In such a scenario, a picture is indeed worth more than a thousand words. Note that this commit is more complicated than anyone would like, for two reasons: - While PowerShell is the right tool for the job, a PowerShell step in GitHub Actions will pop up a Terminal window, _hiding_ what we want to screenshot. To work around that, I tried to run things in a Bash step. _Also_ opens a Terminal window! Node.js to the rescue. - _Of course_ it is complicated to take a screenshot. The challenge is to figure out the dimensions of the screen, which should be as easy as looking at `[System.Windows.Forms.Screen]::PrimaryScreen`'s `Bounds` attribute. Easy peasy, right? No, it's not. Most machines nowadays have a _ridiculous_ resolution which is why most setups have a _zoom factor_. Getting to that factor should be trivial, by calling `GetDeviceCaps(hDC, LOGPIXELSX)`, but that's not working in modern Windows! There is a per-monitor display scaling ("DPI"). But even _that_ is hard to get at, calling `GetDpiForMonitor()` will still return 96 DPI (i.e. 100% zoom) because PowerShell is not marked as _Per-Monitor DPI Aware_. Since we do not want to write a manifest into the same directory as `powershell.exe` resides, we have to jump through yet another hoop to get that. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent b2016c4 commit 0dd7225

File tree

1 file changed

+69
-0
lines changed

1 file changed

+69
-0
lines changed

.github/workflows/ui-tests.yml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,75 @@ jobs:
9393
if: cancelled()
9494
working-directory: ui-tests
9595
run: type bg-hook.log
96+
- name: Take screenshot, if canceled
97+
id: take-screenshot
98+
if: cancelled() || failure()
99+
uses: actions/github-script@v7 # `pwsh` and `bash` open a Terminal window!
100+
with:
101+
script: |
102+
const powershell = `
103+
Add-Type -TypeDefinition @"
104+
using System;
105+
using System.Runtime.InteropServices;
106+
107+
public class DpiHelper {
108+
[DllImport("user32.dll")]
109+
public static extern bool SetProcessDpiAwarenessContext(IntPtr dpiContext);
110+
111+
[DllImport("Shcore.dll")]
112+
public static extern int GetDpiForMonitor(IntPtr hmonitor, int dpiType, out uint dpiX, out uint dpiY);
113+
114+
[DllImport("User32.dll")]
115+
public static extern IntPtr MonitorFromPoint(System.Drawing.Point pt, uint dwFlags);
116+
117+
public static uint GetDPI() {
118+
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4
119+
SetProcessDpiAwarenessContext((IntPtr)(-4));
120+
121+
uint dpiX, dpiY;
122+
IntPtr monitor = MonitorFromPoint(new System.Drawing.Point(0, 0), 2); // MONITOR_DEFAULTTONEAREST
123+
GetDpiForMonitor(monitor, 0, out dpiX, out dpiY); // MDT_EFFECTIVE_DPI
124+
return (dpiX + dpiY) / 2;
125+
}
126+
}
127+
"@ -ReferencedAssemblies "System.Drawing.dll"
128+
129+
$dpi = [DpiHelper]::GetDPI()
130+
131+
[Reflection.Assembly]::LoadWithPartialName("System.Drawing")
132+
function screenshot([Drawing.Rectangle]$bounds, $path) {
133+
$bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
134+
$graphics = [Drawing.Graphics]::FromImage($bmp)
135+
$graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
136+
$bmp.Save($path)
137+
$graphics.Dispose()
138+
$bmp.Dispose()
139+
}
140+
Add-Type -AssemblyName System.Windows.Forms
141+
$screen = [System.Windows.Forms.Screen]::PrimaryScreen
142+
$bounds = [Drawing.Rectangle]::FromLTRB(0, 0, $screen.Bounds.Width * $dpi / 96, $screen.Bounds.Height * $dpi / 96)
143+
screenshot $bounds "ui-tests/screenshot.png"
144+
`
145+
146+
const { spawn } = require('child_process')
147+
148+
const child = spawn('powershell.exe', ['-Command', powershell], {
149+
windowsHide: true,
150+
shell: false
151+
})
152+
153+
child.stdout.on('data', (data) => {
154+
console.log(`Output: ${data}`)
155+
})
156+
157+
child.stderr.on('data', (data) => {
158+
console.error(`Error: ${data}`)
159+
})
160+
161+
child.on('exit', (code) => {
162+
console.log(`Script exited with code ${code}`)
163+
process.exit(code)
164+
})
96165
- name: Upload test results
97166
if: always()
98167
uses: actions/upload-artifact@v4

0 commit comments

Comments
 (0)