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