Refactor SSD app automation with PowerShell integration

Refactor Simply Sign Desktop app automation script to use native PowerShell for keystrokes and screenshots. Remove deprecated methods and improve error handling.
This commit is contained in:
Jay Lee
2026-03-04 15:58:17 -05:00
committed by GitHub
parent f548d49e19
commit dab6272d55

View File

@@ -1,141 +1,115 @@
// Node.js script that implements an Appium client which will launch // Node.js script to launch Simply Sign Desktop app and log a user in
// Simply Sign Desktop app and log a user in. Once logged in it should // using native Windows keystrokes and native PowerShell screenshots.
// be possible to use tools like signtool.exe to sign Windows EXE/MSI files
// with the Certum certificate.
import { Key, remote } from 'webdriverio'; import { exec, execSync } from 'child_process';
import { exec } from 'child_process';
import { TOTP } from 'totp-generator'; import { TOTP } from 'totp-generator';
async function screenshot(driver, filename) {
// uncomment to save .png screenshots
await driver.saveScreenshot(filename);
return
}
function sleep(ms) { function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
} }
async function executeCommand(command) { // Native PowerShell Keystroke Sender
try { function sendKeys(keys) {
let { stdout, stderr } = await exec(command); const script = `$wshell = New-Object -ComObject wscript.shell; $wshell.SendKeys('${keys}')`;
return stdout; execSync(`powershell -Command "${script}"`);
} catch (error) {
console.error(`Error executing command: ${command}`);
console.error(`Error details: ${error}`);
throw error;
}
} }
async function performKeys(driver, keysArray) { // NEW: Native PowerShell Screen Capture
// WinAppDriver doesn't support W3C Actions for keys. function takeScreenshot(filename) {
// We are forced to use the deprecated method. const psScript = `
await driver.sendKeys(keysArray); Add-Type -AssemblyName System.Windows.Forms;
Add-Type -AssemblyName System.Drawing;
$Screen = [System.Windows.Forms.SystemInformation]::VirtualScreen;
$bitmap = New-Object System.Drawing.Bitmap $Screen.Width, $Screen.Height;
$graphic = [System.Drawing.Graphics]::FromImage($bitmap);
$graphic.CopyFromScreen($Screen.Left, $Screen.Top, 0, 0, $bitmap.Size);
$bitmap.Save('${filename}');
`;
try {
execSync(`powershell -Command "${psScript}"`);
console.log(`Saved screenshot: ${filename}`);
} catch (err) {
console.error(`Failed to save screenshot ${filename}:`, err.message);
}
} }
async function runSSD() { async function runSSD() {
const opts = { console.log('Launching SimplySign Desktop...');
port: 4723,
logLevel: "silent",
capabilities: {
platformName: "Windows",
"appium:app": "C:\\Program Files\\Certum\\SimplySign Desktop\\SimplySignDesktop.exe",
"appium:automationName": "Windows",
},
};
let driver; // Launch the application.
try { exec('"C:\\Program Files\\Certum\\SimplySign Desktop\\SimplySignDesktop.exe"', (error) => {
driver = await remote(opts); if (error) console.error(`exec error: ${error}`);
});
// Github Actions Win ARM64 is stuck on a OOB screen that steals focus // 1. Handle ARM64 Out-Of-Box experience
// These enter / escapes should dismiss it.
const runner_arch = process.env.RUNNER_ARCH; const runner_arch = process.env.RUNNER_ARCH;
if (runner_arch === "ARM64") { if (runner_arch === "ARM64") {
console.log('Running on ARM64...'); console.log('Running on ARM64...');
await sleep(3000); // Pause execution for 3 seconds await sleep(3000);
await screenshot(driver, 'oob1.png'); takeScreenshot('oob1.png');
await performKeys(driver, [Key.Enter]); sendKeys('{ENTER}');
await sleep(3000); // Pause execution for 3 seconds
await screenshot(driver, 'oob2.png'); await sleep(3000);
await performKeys(driver, [Key.Enter]); takeScreenshot('oob2.png');
await sleep(3000); // Pause execution for 3 seconds sendKeys('{ENTER}');
await screenshot(driver, 'oob3.png');
await performKeys(driver, [Key.Escape]); await sleep(3000);
await screenshot(driver, 'oob6.png'); takeScreenshot('oob3.png');
sendKeys('{ESC}');
takeScreenshot('oob6.png');
// Re-execute SSD to open login dialog
exec('"C:\\Program Files\\Certum\\SimplySign Desktop\\SimplySignDesktop.exe"');
} else { } else {
console.log('NOT running on ARM64'); console.log('NOT running on ARM64');
} }
// Execute SSD again to open login dialog
exec('"C:\\Program Files\\Certum\\SimplySign Desktop\\SimplySignDesktop.exe"', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
});
await sleep(3000); await sleep(3000);
// Login // 2. Login Flow
const windows = await driver.getWindowHandles(); takeScreenshot('login01.png');
const login_window = windows[0] console.log('Typing credentials...');
await driver.switchWindow(login_window);
await screenshot(driver, 'login01.png');
const id_value = 'jay0lee@gmail.com';
const id_arr = [...id_value];
// Using the new helper for string arrays // Type Email
await performKeys(driver, id_arr); sendKeys('jay0lee@gmail.com');
await screenshot(driver, 'login02.png'); await sleep(500);
takeScreenshot('login02.png');
await performKeys(driver, [Key.Tab]); // Tab to next field
sendKeys('{TAB}');
await sleep(500);
console.log('Our secret is ' + process.env.TOTP_SECRET.length + ' characters.'); // Generate and type TOTP
// We wait until the last possible second to generate console.log(`Our secret is ${process.env.TOTP_SECRET.length} characters.`);
// our TOTP to ensure it's still valid.
const { otp } = await TOTP.generate(process.env.TOTP_SECRET, {algorithm: 'SHA-256'}); const { otp } = await TOTP.generate(process.env.TOTP_SECRET, {algorithm: 'SHA-256'});
console.log('Our token is ' + otp.length + ' characters.'); console.log(`Our token is ${otp.length} characters.`);
const otp_arr = [...otp];
// Using the new helper for the OTP string array sendKeys(otp);
await performKeys(driver, otp_arr); await sleep(500);
await screenshot(driver, 'login03.png'); takeScreenshot('login03.png');
await performKeys(driver, [Key.Enter]); // Submit
sendKeys('{ENTER}');
console.log('Login sequence complete.');
// TODO: it's expected that on successful login the window // Original screenshot cascade to monitor the window closing
// will close and these screenshots will error out. Figure takeScreenshot('login04.png');
// out how to handle that gracefully.
await screenshot(driver, 'login04.png');
await sleep(500); await sleep(500);
await screenshot(driver, 'login05.png'); takeScreenshot('login05.png');
await sleep(500); await sleep(500);
await screenshot(driver, 'login06.png'); takeScreenshot('login06.png');
await sleep(500); await sleep(500);
await screenshot(driver, 'login07.png'); takeScreenshot('login07.png');
await sleep(500); await sleep(500);
await screenshot(driver, 'login08.png'); takeScreenshot('login08.png');
await sleep(500); await sleep(500);
await screenshot(driver, 'login09.png'); takeScreenshot('login09.png');
await sleep(500); await sleep(500);
await screenshot(driver, 'login10.png'); takeScreenshot('login10.png');
await sleep(500); await sleep(500);
await screenshot(driver, 'login11.png'); takeScreenshot('login11.png');
await sleep(500); await sleep(500);
await screenshot(driver, 'login12.png'); takeScreenshot('login12.png');
} catch (error) {
console.error(error);
//console.error("Error during Appium run:");
}
// INTENTIONAL Keep driver open so tray icon for Certum doesn't close
// finally {
// if (driver) {
// await driver.deleteSession(); // Close the Appium session
// }
//}
} }
runSSD(); runSSD();