1212// Run: npx tsx tests/e2e/a11y-nvda-smoke.ts
1313import { chromium } from "@playwright/test" ;
1414import { nvda , WindowsKeyCodes , WindowsModifiers } from "@guidepup/guidepup" ;
15+ import { execSync } from "node:child_process" ;
1516import { mkdirSync , writeFileSync } from "node:fs" ;
1617
18+ // A transient "Windows Security" credential dialog (hosted by PickerHost.exe)
19+ // can seize the foreground on the CI desktop; NVDA then reads only it. It's a
20+ // throwaway UI host — kill it so focus can return to the browser.
21+ function dismissSecurityDialog ( ) {
22+ for ( const im of [ "PickerHost.exe" , "CredentialUIBroker.exe" , "consent.exe" ] ) {
23+ try {
24+ execSync ( `taskkill /im ${ im } /f` , { stdio : "ignore" } ) ;
25+ } catch {
26+ /* not running — fine */
27+ }
28+ }
29+ }
30+
1731const PAGE =
1832 "data:text/html," +
1933 encodeURIComponent (
@@ -42,6 +56,11 @@ async function focusBrowser(pageTitle: string): Promise<{ ok: boolean; seen: str
4256 const title = clean ( await nvda . lastSpokenPhrase ( ) ) ;
4357 seen . push ( title ) ;
4458 if ( want . length && ( title . startsWith ( want ) || title . includes ( "chrom" ) ) ) return { ok : true , seen } ;
59+ if ( title . includes ( "security" ) || title . includes ( "credential" ) ) {
60+ dismissSecurityDialog ( ) ; // kill the picker dialog, then re-read (don't Alt+Esc away)
61+ await sleep ( 700 ) ;
62+ continue ;
63+ }
4564 await nvda . perform ( SWITCH , { capture : false } ) ;
4665 await sleep ( 700 ) ;
4766 }
@@ -60,6 +79,8 @@ async function main() {
6079 await page . goto ( PAGE ) ;
6180 await sleep ( 1000 ) ;
6281
82+ dismissSecurityDialog ( ) ; // clear any credential dialog holding the foreground
83+ await sleep ( 500 ) ;
6384 const focused = await focusBrowser ( "NVDA smoke" ) ;
6485 console . log ( "focusBrowser ok=" + focused . ok + " titles seen: " + JSON . stringify ( focused . seen ) ) ;
6586 await nvda . clearSpokenPhraseLog ( ) ;
0 commit comments