Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use CreateProcessW directly and other cleanup. #51

Merged
merged 3 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions Sources/Print.swift

This file was deleted.

5 changes: 0 additions & 5 deletions Sources/WinAppDriver+Requests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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!)
}
Expand Down
62 changes: 35 additions & 27 deletions Sources/WinAppDriver.swift
Original file line number Diff line number Diff line change
@@ -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<STARTUPINFOW>.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<WCHAR>(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)
}
}

Expand Down
6 changes: 1 addition & 5 deletions Sources/WindowsUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ func isProcessRunning(withName processName: String) -> Bool {
}
} while bytesReturned == DWORD(processIds.count * MemoryLayout<DWORD>.size)

let processCount = Int(bytesReturned) / MemoryLayout<DWORD>.size

for i in 0..<processCount {
let processId = processIds[i]
for processId in processIds {
guard let processHandle = OpenProcess(DWORD(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ), false, processId) else {
continue
}
Expand All @@ -28,7 +25,6 @@ func isProcessRunning(withName processName: String) -> 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
}
Expand Down
3 changes: 0 additions & 3 deletions Tests/WebDriverTests/NotepadTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
6 changes: 1 addition & 5 deletions Tests/WebDriverTests/SessionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
6 changes: 4 additions & 2 deletions Tests/WebDriverTests/StatusTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down