diff --git a/Sources/Print.swift b/Sources/Print.swift deleted file mode 100644 index c72a188..0000000 --- a/Sources/Print.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -// stdout is not flush when we crash -// Use this utility functon to force flush -func printAndFlush(_ msg: String) { - print(msg) - fflush(stdout) -} diff --git a/Sources/WinAppDriver+Requests.swift b/Sources/WinAppDriver+Requests.swift index c85da7b..d97a5e3 100644 --- a/Sources/WinAppDriver+Requests.swift +++ b/Sources/WinAppDriver+Requests.swift @@ -10,11 +10,6 @@ extension WinAppDriver { /// - Returns: new Session instance public func newSession(app: String, appArguments: [String]? = nil, appWorkingDir: String? = nil, waitForAppLaunch: Int? = nil) -> Session { let args = appArguments?.joined(separator: " ") - - print("Starting: \(app)") - print("Arguments: \(args as String? ?? "(None)")") - printAndFlush("Working Dir: \(appWorkingDir as String? ?? "(None)")") - let newSessionRequest = NewSessionRequest(app: app, appArguments: args, appWorkingDir: appWorkingDir, waitForAppLaunch: waitForAppLaunch) return Session(in: self, id: try! send(newSessionRequest).sessionId!) } diff --git a/Sources/WinAppDriver.swift b/Sources/WinAppDriver.swift index c6d6669..9606d87 100644 --- a/Sources/WinAppDriver.swift +++ b/Sources/WinAppDriver.swift @@ -1,51 +1,59 @@ import Foundation import WinSDK +enum WinAppDriverError: Error { + case win32Error(lastError: Int) +} + public class WinAppDriver: WebDriver { static let ip = "127.0.0.1" static let port = 4723 let httpWebDriver: HTTPWebDriver - struct RunningProcess { - var process: Process - var toStdinPipe: Pipe - } - - var runningProcess: RunningProcess? = nil + private var wadProcessInfo: PROCESS_INFORMATION? public init() throws { httpWebDriver = HTTPWebDriver(endpoint: URL(string: "http://\(Self.ip):\(Self.port)")!) - // We start WinAppDriver only if its process is not already started - // CI machines start it using a GitHub action before running the tests - // to get around https://linear.app/the-browser-company/issue/WIN-569/winappdriver-does-not-work-on-ci + // Ensure WinAppDriver is running. if !isProcessRunning(withName: "WinAppDriver.exe") { - print("Starting WinAppDriver...") let path = "\(ProcessInfo.processInfo.environment["ProgramFiles(x86)"]!)\\Windows Application Driver\\WinAppDriver.exe" + let commandLine = ["\"\(path)\"", Self.ip, String(Self.port)].joined(separator: " ") + try commandLine.withCString(encodedAs: UTF16.self) { commandLine throws in + var startupInfo = STARTUPINFOW() + startupInfo.cb = DWORD(MemoryLayout.size) - let process = Process() - let pipe = Pipe() - process.executableURL = URL(fileURLWithPath: path) - process.arguments = [Self.ip, String(Self.port)] - process.standardInput = pipe.fileHandleForReading - process.standardOutput = nil - runningProcess = RunningProcess(process: process, toStdinPipe: pipe) - do { - try runningProcess!.process.run() - } catch { - fatalError("Could not start WinAppDriver; is it installed?") + var processInfo = PROCESS_INFORMATION() + guard CreateProcessW( + nil, + UnsafeMutablePointer(mutating: commandLine), + nil, + nil, + false, + DWORD(CREATE_NEW_CONSOLE), + nil, + nil, + &startupInfo, + &processInfo + ) else { + throw WinAppDriverError.win32Error(lastError: Int(GetLastError())) + } + + wadProcessInfo = processInfo } - } else { - print("WinAppDriver is already running.") } } deinit { - // WinAppDriver responds waits for a key to return - if let runningProcess { - try? runningProcess.toStdinPipe.fileHandleForWriting.write(contentsOf: "\n".data(using: .utf8)!) - runningProcess.process.terminate() + if let wadProcessInfo { + CloseHandle(wadProcessInfo.hThread) + + if !TerminateProcess(wadProcessInfo.hProcess, 0) { + let error = GetLastError() + assertionFailure("TerminateProcess failed with error \(error).") + } + CloseHandle(wadProcessInfo.hProcess) } } diff --git a/Sources/WindowsUtilities.swift b/Sources/WindowsUtilities.swift index cc04658..7430666 100644 --- a/Sources/WindowsUtilities.swift +++ b/Sources/WindowsUtilities.swift @@ -13,10 +13,7 @@ func isProcessRunning(withName processName: String) -> Bool { } } while bytesReturned == DWORD(processIds.count * MemoryLayout.size) - let processCount = Int(bytesReturned) / MemoryLayout.size - - for i in 0.. Bool { var processNameBuffer: [WCHAR] = Array(repeating: 0, count: Int(MAX_PATH)) if K32GetModuleBaseNameW(processHandle, nil, &processNameBuffer, DWORD(processNameBuffer.count)) > 0 { let processNameString = String(decodingCString: processNameBuffer, as: UTF16.self) - if processNameString.lowercased() == processName.lowercased() { return true } diff --git a/Tests/WebDriverTests/NotepadTests.swift b/Tests/WebDriverTests/NotepadTests.swift index 0f84459..9eb5d12 100644 --- a/Tests/WebDriverTests/NotepadTests.swift +++ b/Tests/WebDriverTests/NotepadTests.swift @@ -60,15 +60,12 @@ class Notepad { } class NotepadTests: XCTestCase { - // Use a single WinAppDriver process to avoid incurring the process start/end cost for every test static var winAppDriver: WinAppDriver! - // Called once before all the tests in this class override public class func setUp() { winAppDriver = try! WinAppDriver() } - // Called once after all tests in this class have run override public class func tearDown() { winAppDriver = nil } diff --git a/Tests/WebDriverTests/SessionTests.swift b/Tests/WebDriverTests/SessionTests.swift index 1d3b262..2f807e8 100644 --- a/Tests/WebDriverTests/SessionTests.swift +++ b/Tests/WebDriverTests/SessionTests.swift @@ -5,19 +5,15 @@ import XCTest class SessionTests: XCTestCase { static var session: Session! - // Called once before all the tests in this class override public class func setUp() { - // Use a single WinAppDriver process to avoid incurring the process start/end cost for every test let winAppDriver = try! WinAppDriver() - // We don't store webDriver as session maintains it alive + // We don't store webDriver as session maintains a reference. let windowsDir = ProcessInfo.processInfo.environment["SystemRoot"]! session = winAppDriver.newSession(app: "\(windowsDir)\\System32\\msinfo32.exe") } - // Called once after all tests in this class have run override public class func tearDown() { - // Force the destruction of the session session = nil } diff --git a/Tests/WebDriverTests/StatusTests.swift b/Tests/WebDriverTests/StatusTests.swift index 4ae589f..9409052 100644 --- a/Tests/WebDriverTests/StatusTests.swift +++ b/Tests/WebDriverTests/StatusTests.swift @@ -4,12 +4,14 @@ import XCTest class StatusTest: XCTestCase { static var winAppDriver: WinAppDriver! - // Called once before all the tests in this class override public class func setUp() { - // Use a single WinAppDriver process to avoid incurring the process start/end cost for every test winAppDriver = try! WinAppDriver() } + override public class func tearDown() { + winAppDriver = nil + } + // test that status returns reasonable answers func testStatus() { let status = try! Self.winAppDriver.status