diff --git a/Docs/SupportedAPIs.md b/Docs/SupportedAPIs.md index 08d4d05..cb258d1 100644 --- a/Docs/SupportedAPIs.md +++ b/Docs/SupportedAPIs.md @@ -37,6 +37,8 @@ Contributions to expand support to unimplemented functionality are always welcom | GET | `/session/:sessionId/element/:id/size` | Supported | `Element.size` | | GET | `/session/:sessionId/element/:id/text` | Supported | `Element.text` | | POST | `/session/:sessionId/element/:id/value` | Supported | `Element.sendKeys()`| +| POST | `/session/:sessionId/execute` | Not Supported| `Session.execute()` | +| POST | `/session/:sessionId/execute_async` | Not Supported| `Session.execute()` | | POST | `/session/:sessionId/forward` | Supported | `Session.forward()` | | POST | `/session/:sessionId/keys` | Supported | `Session.sendKeys()`| | GET | `/session/:sessionId/location` | Supported | Not implemented | @@ -44,7 +46,7 @@ Contributions to expand support to unimplemented functionality are always welcom | GET | `/session/:sessionId/orientation` | Supported | `Session.orientation`| | POST | `/session/:sessionId/refresh` | Not supported| `Session.refresh()` | | GET | `/session/:sessionId/screenshot` | Supported | `Session.screenshot()`| -| GET | `/session/:sessionId/source` | Supported | Not implemented | +| GET | `/session/:sessionId/source` | Supported | `Session.source` | | POST | `/session/:sessionId/timeouts` | Supported | `Session.setTimeout()`| | GET | `/session/:sessionId/title` | Supported | `Session.title` | | POST | `/session/:sessionId/touch/click` | Supported | `Element.touchClick()`| @@ -68,4 +70,4 @@ Contributions to expand support to unimplemented functionality are always welcom | GET | `/session/:sessionId/window/:windowHandle/position` | Supported | Not implemented | | POST | `/session/:sessionId/window/:windowHandle/maximize` | Supported | Not implemented | | GET | `/session/:sessionId/window_handle` | Supported | Not implemented | -| GET | `/session/:sessionId/window_handles` | Supported | Not implemented | \ No newline at end of file +| GET | `/session/:sessionId/window_handles` | Supported | Not implemented | diff --git a/Package.swift b/Package.swift index c03fdfa..a32ec4d 100644 --- a/Package.swift +++ b/Package.swift @@ -5,28 +5,37 @@ import PackageDescription let package = Package( name: "swift-webdriver", products: [ - .library(name: "WebDriver", targets: ["WebDriver", "WinAppDriver"]), + .library(name: "WebDriver", targets: ["WebDriver"]), ], targets: [ .target( name: "WebDriver", path: "Sources/WebDriver"), - .target( - name: "WinAppDriver", - dependencies: ["WebDriver"], - path: "Sources/WinAppDriver"), .target( name: "TestsCommon", path: "Tests/Common"), - .testTarget( - name: "WinAppDriverTests", - dependencies: ["TestsCommon", "WebDriver", "WinAppDriver"], - // Ignore "LNK4217: locally defined symbol imported" spew due to SPM library support limitations - linkerSettings: [ .unsafeFlags(["-Xlinker", "-ignore:4217"]) ]), .testTarget( name: "UnitTests", - dependencies: ["TestsCommon", "WebDriver", "WinAppDriver"], + dependencies: ["TestsCommon", "WebDriver"], // Ignore "LNK4217: locally defined symbol imported" spew due to SPM library support limitations - linkerSettings: [ .unsafeFlags(["-Xlinker", "-ignore:4217"]) ]), + linkerSettings: [ .unsafeFlags(["-Xlinker", "-ignore:4217"], .when(platforms: [.windows])) ]), ] ) + +#if os(Windows) +package.products += [ + .library(name: "WinAppDriver", targets: ["WinAppDriver"]) +] +package.targets += [ + .target( + name: "WinAppDriver", + dependencies: ["WebDriver"], + path: "Sources/WinAppDriver"), + .testTarget( + name: "WinAppDriverTests", + dependencies: ["TestsCommon", "WebDriver", "WinAppDriver"], + // Ignore "LNK4217: locally defined symbol imported" spew due to SPM library support limitations + linkerSettings: [ .unsafeFlags(["-Xlinker", "-ignore:4217"]) ]), +] +#endif + diff --git a/Sources/WebDriver/HTTPWebDriver.swift b/Sources/WebDriver/HTTPWebDriver.swift index 064266e..acd6a81 100644 --- a/Sources/WebDriver/HTTPWebDriver.swift +++ b/Sources/WebDriver/HTTPWebDriver.swift @@ -1,5 +1,7 @@ import Foundation +#if canImport(FoundationNetworking) import FoundationNetworking +#endif public struct HTTPWebDriver: WebDriver { let rootURL: URL diff --git a/Sources/WebDriver/Requests.swift b/Sources/WebDriver/Requests.swift index a45b360..be4dffd 100644 --- a/Sources/WebDriver/Requests.swift +++ b/Sources/WebDriver/Requests.swift @@ -462,6 +462,23 @@ public enum Requests { } } + // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidexecute + // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidexecute_async + public struct SessionScript: Request { + public var session: String + public var script: String + public var args: [String] + public var async: Bool + + public var pathComponents: [String] { ["session", session, async ? "execute_async" : "execute"] } + public var method: HTTPMethod { .post } + public var body: Body { .init(script: script, args: args) } + public struct Body: Codable { + public var script: String + public var args: [String] + } + } + // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidwindow public enum SessionWindow { public struct Post: Request { @@ -524,6 +541,21 @@ public enum Requests { } } + // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidsource + public struct SessionSource: Request { + public var session: String + + public var pathComponents: [String] { ["session", session, "source"] } + public var method: HTTPMethod {.get} + + public typealias Response = ResponseWithValue + + public struct ResponseValue: Codable { + public var source: String + } + + } + // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#status public struct Status: Request { public var pathComponents: [String] { ["status"] } diff --git a/Sources/WebDriver/Session.swift b/Sources/WebDriver/Session.swift index 695fb38..312f2bc 100644 --- a/Sources/WebDriver/Session.swift +++ b/Sources/WebDriver/Session.swift @@ -73,6 +73,10 @@ public class Session { Requests.SessionTimeouts(session: id, type: type, ms: duration * 1000)) } + public func execute(script: String, args: [String] = [], async: Bool = false) throws { + try webDriver.send(Requests.SessionScript(session: id, script: script, args: args, async: async)) + } + public func back() throws { try webDriver.send(Requests.SessionBack(session: id)) } @@ -333,6 +337,15 @@ public class Session { return response.value.screenOrientation } + + /// - Returns: The current page source. + public func source() throws -> String { + let response = try webDriver.send(Requests.SessionSource(session: id)) + return response.value.source + } + + /// Deletes the current session. + public func delete() throws { guard shouldDelete else { return } try webDriver.send(Requests.SessionDelete(session: id)) diff --git a/Sources/WebDriver/URLRequestExtensions.swift b/Sources/WebDriver/URLRequestExtensions.swift index 1739de5..e9edf88 100644 --- a/Sources/WebDriver/URLRequestExtensions.swift +++ b/Sources/WebDriver/URLRequestExtensions.swift @@ -1,5 +1,7 @@ import Foundation +#if canImport(FoundationNetworking) import FoundationNetworking +#endif extension URLSession { func dataTask( diff --git a/Sources/WinAppDriver/ErrorResponse+WinAppDriver.swift b/Sources/WinAppDriver/ErrorResponse+WinAppDriver.swift index 80f288e..7f49d8f 100644 --- a/Sources/WinAppDriver/ErrorResponse+WinAppDriver.swift +++ b/Sources/WinAppDriver/ErrorResponse+WinAppDriver.swift @@ -1,3 +1,5 @@ +import WebDriver + extension ErrorResponse.Status { // WinAppDriver returns when passing an incorrect window handle to attach to. static let winAppDriver_invalidArgument = Self(rawValue: 100) diff --git a/Tests/UnitTests/APIToRequestMappingTests.swift b/Tests/UnitTests/APIToRequestMappingTests.swift index 15b8283..41a9828 100644 --- a/Tests/UnitTests/APIToRequestMappingTests.swift +++ b/Tests/UnitTests/APIToRequestMappingTests.swift @@ -170,6 +170,20 @@ class APIToRequestMappingTests: XCTestCase { XCTAssert(try element.enabled == true) } + func testSessionScript() throws { + let mockWebDriver = MockWebDriver() + let session = Session(webDriver: mockWebDriver, existingId: "mySession") + mockWebDriver.expect(path: "session/mySession/execute", method: .post) + XCTAssertNotNil(try session.execute(script: "return document.body", args: ["script"], async: false)) + } + + func testSessionScriptAsync() throws { + let mockWebDriver = MockWebDriver() + let session = Session(webDriver: mockWebDriver, existingId: "mySession") + mockWebDriver.expect(path: "session/mySession/execute_async", method: .post) + XCTAssertNotNil(try session.execute(script: "return document.body", args: ["script"], async: true)) + } + func testSessionTouchScroll() throws { let mockWebDriver: MockWebDriver = MockWebDriver() let session = Session(webDriver: mockWebDriver, existingId: "mySession") @@ -200,6 +214,7 @@ class APIToRequestMappingTests: XCTestCase { XCTAssert(try session.size(window: "myWindow") == (width: 500, height: 500)) } + func testWindowOrientation() throws { let mockWebDriver: MockWebDriver = MockWebDriver() let session = Session(webDriver: mockWebDriver, existingId: "mySession") @@ -220,4 +235,14 @@ class APIToRequestMappingTests: XCTestCase { XCTAssert(try session.orientation() == "LANDSCAPE") } + + func testSessionSource() throws { + let mockWebDriver: MockWebDriver = MockWebDriver() + let session = Session(webDriver: mockWebDriver, existingId: "mySession") + + mockWebDriver.expect(path: "session/mySession/source", method: .get, type: Requests.SessionSource.self) { + ResponseWithValue(.init(source: "currentSource")) + } + XCTAssert(try session.source() == "currentSource") + } } diff --git a/Tests/UnitTests/CommandLineTests.swift b/Tests/WinAppDriverTests/CommandLineTests.swift similarity index 100% rename from Tests/UnitTests/CommandLineTests.swift rename to Tests/WinAppDriverTests/CommandLineTests.swift