Skip to content

Commit 995e362

Browse files
committed
amend! amend! ci(ui-tests): take a screenshot when canceled
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 2ebd8f5 commit 995e362

File tree

1 file changed

+65
-45
lines changed

1 file changed

+65
-45
lines changed

.github/workflows/ui-tests.yml

Lines changed: 65 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -134,51 +134,71 @@ jobs:
134134
- name: Take screenshot, if canceled
135135
id: take-screenshot
136136
if: cancelled() || failure()
137-
shell: bash # `pwsh` opens a Terminal window; `bash` does not!
138-
run: |
139-
powershell -command '
140-
Add-Type -TypeDefinition @"
141-
using System;
142-
using System.Runtime.InteropServices;
143-
144-
public class DpiHelper {
145-
[DllImport("user32.dll")]
146-
public static extern bool SetProcessDpiAwarenessContext(IntPtr dpiContext);
147-
148-
[DllImport("Shcore.dll")]
149-
public static extern int GetDpiForMonitor(IntPtr hmonitor, int dpiType, out uint dpiX, out uint dpiY);
150-
151-
[DllImport("User32.dll")]
152-
public static extern IntPtr MonitorFromPoint(System.Drawing.Point pt, uint dwFlags);
153-
154-
public static uint GetDPI() {
155-
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4
156-
SetProcessDpiAwarenessContext((IntPtr)(-4));
157-
158-
uint dpiX, dpiY;
159-
IntPtr monitor = MonitorFromPoint(new System.Drawing.Point(0, 0), 2); // MONITOR_DEFAULTTONEAREST
160-
GetDpiForMonitor(monitor, 0, out dpiX, out dpiY); // MDT_EFFECTIVE_DPI
161-
return (dpiX + dpiY) / 2;
162-
}
163-
}
164-
"@ -ReferencedAssemblies "System.Drawing.dll"
165-
166-
$dpi = [DpiHelper]::GetDPI()
167-
168-
[Reflection.Assembly]::LoadWithPartialName("System.Drawing")
169-
function screenshot([Drawing.Rectangle]$bounds, $path) {
170-
$bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
171-
$graphics = [Drawing.Graphics]::FromImage($bmp)
172-
$graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
173-
$bmp.Save($path)
174-
$graphics.Dispose()
175-
$bmp.Dispose()
176-
}
177-
Add-Type -AssemblyName System.Windows.Forms
178-
$screen = [System.Windows.Forms.Screen]::PrimaryScreen
179-
$bounds = [Drawing.Rectangle]::FromLTRB(0, 0, $screen.Bounds.Width * $dpi / 96, $screen.Bounds.Height * $dpi / 96)
180-
screenshot $bounds "screenshot.png"
181-
'
137+
uses: actions/github-script@v7 # `pwsh` and `bashe open a Terminal window!
138+
with:
139+
script: |
140+
const powershell = `
141+
Add-Type -TypeDefinition @"
142+
using System;
143+
using System.Runtime.InteropServices;
144+
145+
public class DpiHelper {
146+
[DllImport("user32.dll")]
147+
public static extern bool SetProcessDpiAwarenessContext(IntPtr dpiContext);
148+
149+
[DllImport("Shcore.dll")]
150+
public static extern int GetDpiForMonitor(IntPtr hmonitor, int dpiType, out uint dpiX, out uint dpiY);
151+
152+
[DllImport("User32.dll")]
153+
public static extern IntPtr MonitorFromPoint(System.Drawing.Point pt, uint dwFlags);
154+
155+
public static uint GetDPI() {
156+
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4
157+
SetProcessDpiAwarenessContext((IntPtr)(-4));
158+
159+
uint dpiX, dpiY;
160+
IntPtr monitor = MonitorFromPoint(new System.Drawing.Point(0, 0), 2); // MONITOR_DEFAULTTONEAREST
161+
GetDpiForMonitor(monitor, 0, out dpiX, out dpiY); // MDT_EFFECTIVE_DPI
162+
return (dpiX + dpiY) / 2;
163+
}
164+
}
165+
"@ -ReferencedAssemblies "System.Drawing.dll"
166+
167+
$dpi = [DpiHelper]::GetDPI()
168+
169+
[Reflection.Assembly]::LoadWithPartialName("System.Drawing")
170+
function screenshot([Drawing.Rectangle]$bounds, $path) {
171+
$bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
172+
$graphics = [Drawing.Graphics]::FromImage($bmp)
173+
$graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
174+
$bmp.Save($path)
175+
$graphics.Dispose()
176+
$bmp.Dispose()
177+
}
178+
Add-Type -AssemblyName System.Windows.Forms
179+
$screen = [System.Windows.Forms.Screen]::PrimaryScreen
180+
$bounds = [Drawing.Rectangle]::FromLTRB(0, 0, $screen.Bounds.Width * $dpi / 96, $screen.Bounds.Height * $dpi / 96)
181+
screenshot $bounds "screenshot.png"
182+
`
183+
const { spawn } = require('child_process')
184+
185+
const child = spawn('powershell.exe', ['-Command', powershell], {
186+
windowsHide: true,
187+
shell: false
188+
})
189+
190+
child.stdout.on('data', (data) => {
191+
console.log(`Output: ${data}`)
192+
})
193+
194+
child.stderr.on('data', (data) => {
195+
console.error(`Error: ${data}`)
196+
})
197+
198+
child.on('exit', (code) => {
199+
console.log(`Script exited with code ${code}`)
200+
process.exit(code)
201+
})
182202
- name: Upload screenshot, if taken
183203
uses: actions/upload-artifact@v4
184204
if: always() && steps.take-screenshot.conclusion == 'success'

0 commit comments

Comments
 (0)