-
-
Notifications
You must be signed in to change notification settings - Fork 45
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
Combine the WebDriver setup process into WebDriverService and make it easier to reuse #474
Changes from all commits
3b00c68
87aa425
5d33130
c63bb8a
caa74ec
def52ac
aefec30
67ed784
648dba1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
public func withRetry<R>( | ||
maxAttempts: Int, | ||
initialDelay: Duration, | ||
retryInterval: Duration, | ||
body: () async throws -> R | ||
) async throws -> R { | ||
try await Task.sleep(for: initialDelay) | ||
|
||
var attempt = 0 | ||
while true { | ||
attempt += 1 | ||
do { | ||
return try await body() | ||
} catch { | ||
if attempt < maxAttempts { | ||
print("attempt \(attempt)/\(maxAttempts) failed: \(error), retrying in \(retryInterval)...") | ||
|
||
try await Task.sleep(for: retryInterval) | ||
continue | ||
} | ||
|
||
throw error | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import CartonHelpers | ||
import Foundation | ||
import NIOCore | ||
import NIOPosix | ||
|
||
public struct CommandWebDriverService: WebDriverService { | ||
private static func findAvailablePort() async throws -> SocketAddress { | ||
let bootstrap = ServerBootstrap(group: .singletonMultiThreadedEventLoopGroup) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ここの EventLoopGroup だけ NIOPosixの提供するシングルトンに変えた 他はコピペ |
||
let address = try SocketAddress.makeAddressResolvingHost("127.0.0.1", port: 0) | ||
let channel = try await bootstrap.bind(to: address).get() | ||
let localAddr = channel.localAddress! | ||
try await channel.close() | ||
return localAddr | ||
} | ||
|
||
private static func launchDriver( | ||
terminal: InteractiveWriter, | ||
executablePath: String | ||
) async throws -> (URL, CartonHelpers.Process) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Processを返すように変更する |
||
let address = try await findAvailablePort() | ||
let process = CartonHelpers.Process(arguments: [ | ||
executablePath, "--port=\(address.port!)", | ||
]) | ||
terminal.logLookup("Launch WebDriver executable: ", executablePath) | ||
try process.launch() | ||
let url = URL(string: "http://\(address.ipAddress!):\(address.port!)")! | ||
return (url, process) | ||
} | ||
|
||
public static func findFromEnvironment( | ||
terminal: CartonHelpers.InteractiveWriter | ||
) async throws -> CommandWebDriverService? { | ||
terminal.logLookup("- checking WebDriver executable: ", "WEBDRIVER_PATH") | ||
guard let executable = ProcessInfo.processInfo.environment["WEBDRIVER_PATH"] else { | ||
return nil | ||
} | ||
let (endpoint, process) = try await launchDriver( | ||
terminal: terminal, executablePath: executable | ||
) | ||
return CommandWebDriverService(endpoint: endpoint, process: process) | ||
} | ||
|
||
public static func findFromPath( | ||
terminal: CartonHelpers.InteractiveWriter | ||
) async throws -> CommandWebDriverService? { | ||
let driverCandidates = [ | ||
"chromedriver", "geckodriver", "safaridriver", "msedgedriver", | ||
] | ||
terminal.logLookup( | ||
"- checking WebDriver executable in PATH: ", driverCandidates.joined(separator: ", ")) | ||
guard let found = driverCandidates.lazy | ||
.compactMap({ CartonHelpers.Process.findExecutable($0) }).first else | ||
{ | ||
return nil | ||
} | ||
let (endpoint, process) = try await launchDriver( | ||
terminal: terminal, executablePath: found.pathString | ||
) | ||
return CommandWebDriverService(endpoint: endpoint, process: process) | ||
} | ||
|
||
public static func find( | ||
terminal: CartonHelpers.InteractiveWriter | ||
) async throws -> CommandWebDriverService? { | ||
if let driver = try await findFromEnvironment(terminal: terminal) { | ||
return driver | ||
} | ||
|
||
if let driver = try await findFromPath(terminal: terminal) { | ||
return driver | ||
} | ||
|
||
return nil | ||
} | ||
|
||
public init( | ||
endpoint: URL, | ||
process: CartonHelpers.Process | ||
) { | ||
self.endpoint = endpoint | ||
self.process = process | ||
} | ||
|
||
public var endpoint: URL | ||
public var process: CartonHelpers.Process | ||
|
||
public func dispose() { | ||
process.signal(SIGKILL) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. このサービスはコマンドで起動するのでdisposeでKILLするためにProcessを持つ |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// Copyright 2022 Carton contributors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
import Foundation | ||
|
||
#if canImport(FoundationNetworking) | ||
import FoundationNetworking | ||
#endif | ||
|
||
public struct CurlWebDriverHTTPClient: WebDriverHTTPClient { | ||
public init(cliPath: URL) { | ||
self.cliPath = cliPath | ||
} | ||
|
||
public var cliPath: URL | ||
|
||
public static func find() -> CurlWebDriverHTTPClient? { | ||
guard let path = ProcessInfo.processInfo.environment["PATH"] else { return nil } | ||
#if os(Windows) | ||
let pathSeparator: Character = ";" | ||
#else | ||
let pathSeparator: Character = ":" | ||
#endif | ||
for pathEntry in path.split(separator: pathSeparator) { | ||
let candidate = URL(fileURLWithPath: String(pathEntry)).appendingPathComponent("curl") | ||
if FileManager.default.fileExists(atPath: candidate.path) { | ||
return CurlWebDriverHTTPClient(cliPath: candidate) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
public func data(for request: URLRequest) async throws -> Data { | ||
guard let url = request.url?.absoluteString else { | ||
preconditionFailure() | ||
} | ||
let process = Process() | ||
process.executableURL = cliPath | ||
process.arguments = [ | ||
url, "-X", request.httpMethod ?? "GET", "--silent", "--fail-with-body", "--data-binary", "@-" | ||
] | ||
let stdout = Pipe() | ||
let stdin = Pipe() | ||
process.standardOutput = stdout | ||
process.standardInput = stdin | ||
if let httpBody = request.httpBody { | ||
try stdin.fileHandleForWriting.write(contentsOf: httpBody) | ||
} | ||
try stdin.fileHandleForWriting.close() | ||
try process.run() | ||
process.waitUntilExit() | ||
let responseBody = try stdout.fileHandleForReading.readToEnd() | ||
guard process.terminationStatus == 0 else { | ||
let body: String? = responseBody.map { String(decoding: $0, as: UTF8.self) } | ||
|
||
throw WebDriverError.curlError( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ここエラーの内容をリッチにした。 |
||
path: cliPath, | ||
status: process.terminationStatus, | ||
body: body | ||
) | ||
} | ||
return responseBody ?? Data() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import CartonHelpers | ||
import Foundation | ||
|
||
public struct RemoteWebDriverService: WebDriverService { | ||
public static func find( | ||
terminal: InteractiveWriter | ||
) async throws -> RemoteWebDriverService? { | ||
terminal.logLookup("- checking WebDriver endpoint: ", "WEBDRIVER_REMOTE_URL") | ||
guard let value = ProcessInfo.processInfo.environment["WEBDRIVER_REMOTE_URL"] else { | ||
return nil | ||
} | ||
guard let endporint = URL(string: value) else { | ||
throw WebDriverError.invalidRemoteURL(value) | ||
} | ||
return RemoteWebDriverService(endpoint: endporint) | ||
} | ||
|
||
public init(endpoint: URL) { | ||
self.endpoint = endpoint | ||
} | ||
|
||
public var endpoint: URL | ||
|
||
public func dispose() {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. これはリモートのURLを持つだけなので空 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// Copyright 2022 Carton contributors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
import Foundation | ||
|
||
#if canImport(FoundationNetworking) | ||
|
||
import FoundationNetworking | ||
|
||
/// Until we get "async" implementations of URLSession in corelibs-foundation, we use our own polyfill. | ||
extension URLSession { | ||
public func data(for request: URLRequest) async throws -> (Data, URLResponse) { | ||
return try await withCheckedThrowingContinuation { continuation in | ||
let task = self.dataTask(with: request) { (data, response, error) in | ||
guard let data = data, let response = response else { | ||
let error = error ?? URLError(.badServerResponse) | ||
return continuation.resume(throwing: error) | ||
} | ||
continuation.resume(returning: (data, response)) | ||
} | ||
task.resume() | ||
} | ||
} | ||
} | ||
|
||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
コピペで移動してきた