diff --git a/Docs/SupportedAPIs.md b/Docs/SupportedAPIs.md index ebfd848..c22e1ac 100644 --- a/Docs/SupportedAPIs.md +++ b/Docs/SupportedAPIs.md @@ -51,9 +51,9 @@ Contributions to expand support to unimplemented functionality are always welcom | POST | `/session/:sessionId/timeouts` | Supported | `Session.setTimeout()`| | GET | `/session/:sessionId/title` | Supported | `Session.title` | | POST | `/session/:sessionId/touch/click` | Supported | `Element.touchClick()`| -| POST | `/session/:sessionId/touch/doubleclick` | Supported | Not implemented | +| POST | `/session/:sessionId/touch/doubleclick` | Supported | `Element.doubleClick()`| | POST | `/session/:sessionId/touch/down` | Supported | `Session.touchDown()`| -| POST | `/session/:sessionId/touch/flick` | Supported | Not implemented | +| POST | `/session/:sessionId/touch/flick` | Supported | `Session.flick()`, `Element.flick()`| | POST | `/session/:sessionId/touch/longclick` | Supported | Not implemented | | POST | `/session/:sessionId/touch/move` | Supported | `Session.touchMove()`| | POST | `/session/:sessionId/touch/scroll` | Supported | `Session.touchScroll()`| diff --git a/Sources/WebDriver/Element.swift b/Sources/WebDriver/Element.swift index 0a79ed4..22f2b94 100644 --- a/Sources/WebDriver/Element.swift +++ b/Sources/WebDriver/Element.swift @@ -85,6 +85,43 @@ public struct Element { if let notInteractableError = result.value { throw notInteractableError } } + /// Double clicks an element by id. + public func doubleClick(retryTimeout: TimeInterval? = nil) throws { + let request = Requests.SessionTouchDoubleClick(session: session.id, element: id) + let result = try poll(timeout: retryTimeout ?? session.defaultRetryTimeout) { + do { + // Immediately bubble most failures, only retry on element not interactable. + try webDriver.send(request) + return PollResult.success(nil as ErrorResponse?) + } catch let error as ErrorResponse where error.status == .winAppDriver_elementNotInteractable { + return PollResult.failure(error) + } + } + + if let notInteractableError = result.value { throw notInteractableError } + } + + /// - Parameters: + /// - retryTimeout: Optional value to override defaultRetryTimeout. + /// - element: Element id to click + /// - xOffset: The x offset in pixels to flick by. + /// - yOffset: The y offset in pixels to flick by. + /// - speed: The speed in pixels per seconds. + public func flick(xOffset: Double, yOffset: Double, speed: Double, retryTimeout: TimeInterval? = nil) throws { + let request = Requests.SessionTouchFlickElement(session: session.id, element: id, xOffset: xOffset, yOffset: yOffset, speed: speed) + let result = try poll(timeout: retryTimeout ?? session.defaultRetryTimeout) { + do { + // Immediately bubble most failures, only retry on element not interactable. + try webDriver.send(request) + return PollResult.success(nil as ErrorResponse?) + } catch let error as ErrorResponse where error.status == .winAppDriver_elementNotInteractable { + return PollResult.failure(error) + } + } + + if let notInteractableError = result.value { throw notInteractableError } + } + /// Finds an element by id, starting from this element. /// - Parameter byId: id of the element to search for. /// - Parameter retryTimeout: Optional value to override defaultRetryTimeout. diff --git a/Sources/WebDriver/Requests.swift b/Sources/WebDriver/Requests.swift index bc93a29..9013ab4 100644 --- a/Sources/WebDriver/Requests.swift +++ b/Sources/WebDriver/Requests.swift @@ -540,6 +540,66 @@ public enum Requests { } } } + + // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidtouchdoubleclick + public struct SessionTouchDoubleClick: Request { + public var session: String + public var element: String + + public var pathComponents: [String] { ["session", session, "touch", "doubleclick"] } + public var method: HTTPMethod { .post } + public var body: Body { .init(element: element) } + + public struct Body: Codable { + public var element: String + } + } + + // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidtouchflick + public struct SessionTouchFlickElement: Request { + public var session: String + public var element: String + public var xOffset: Double + public var yOffset: Double + public var speed: Double + + public var pathComponents: [String] { ["session", session, "touch", "flick"] } + public var method: HTTPMethod { .post } + public var body: Body { .init(xOffset: xOffset, yOffset: yOffset, speed: speed) } + + public struct Body: Codable { + public var xOffset: Double + public var yOffset: Double + public var speed: Double + + private enum CodingKeys: String, CodingKey { + case xOffset = "xoffset" + case yOffset = "yoffset" + case speed = "speed" + } + } + } + + // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidtouchflick-1 + public struct SessionTouchFlick: Request { + public var session: String + public var xSpeed: Double + public var ySpeed: Double + + public var pathComponents: [String] { ["session", session, "touch", "flick"] } + public var method: HTTPMethod { .post } + public var body: Body { .init(xSpeed: xSpeed, ySpeed: ySpeed) } + + public struct Body: Codable { + public var xSpeed: Double + public var ySpeed: Double + + private enum CodingKeys: String, CodingKey { + case xSpeed = "xspeed" + case ySpeed = "yspeed" + } + } + } // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidlocation public enum SessionLocation { @@ -567,8 +627,7 @@ public enum Requests { public var session: String public var pathComponents: [String] { ["session", session, "source"] } - public var method: HTTPMethod {.get} - + public var method: HTTPMethod { .get } public typealias Response = ResponseWithValue } diff --git a/Sources/WebDriver/Session.swift b/Sources/WebDriver/Session.swift index 688ffad..69b6a0b 100644 --- a/Sources/WebDriver/Session.swift +++ b/Sources/WebDriver/Session.swift @@ -233,6 +233,14 @@ public class Session { }.value } + /// - Parameters: + /// - retryTimeout: Optional value to override defaultRetryTimeout. + /// - xSpeed: The x speed in pixels per second. + /// - ySpeed: The y speed in pixels per second. + public func flick(xSpeed: Double, ySpeed: Double) throws { + try webDriver.send(Requests.SessionTouchFlick(session: id, xSpeed: xSpeed, ySpeed: ySpeed)) + } + /// Moves the pointer to a location relative to the current pointer position or an element. /// - Parameter element: if not nil the top left of the element provides the origin. /// - Parameter xOffset: x offset from the left of the element. diff --git a/Tests/UnitTests/APIToRequestMappingTests.swift b/Tests/UnitTests/APIToRequestMappingTests.swift index 3181aef..783f7f8 100644 --- a/Tests/UnitTests/APIToRequestMappingTests.swift +++ b/Tests/UnitTests/APIToRequestMappingTests.swift @@ -288,6 +288,30 @@ class APIToRequestMappingTests: XCTestCase { XCTAssert(try session.windowHandles == ["myWindow", "myWindow"]) } + + func testElementDoubleClick() throws { + let mockWebDriver: MockWebDriver = MockWebDriver() + let session = Session(webDriver: mockWebDriver, existingId: "mySession") + let element = Element(session: session, id: "myElement") + mockWebDriver.expect(path: "session/mySession/touch/doubleclick", method: .post) + XCTAssertNotNil(try element.doubleClick()) + } + + func testElementFlick() throws { + let mockWebDriver: MockWebDriver = MockWebDriver() + let session = Session(webDriver: mockWebDriver, existingId: "mySession") + let element = Element(session: session, id: "myElement") + mockWebDriver.expect(path: "session/mySession/touch/flick", method: .post) + XCTAssertNotNil(try element.flick(xOffset: 5, yOffset: 20, speed: 2003)) + } + + func testSessionFlick() throws { + let mockWebDriver: MockWebDriver = MockWebDriver() + let session = Session(webDriver: mockWebDriver, existingId: "mySession") + mockWebDriver.expect(path: "session/mySession/touch/flick", method: .post) + XCTAssertNotNil(try session.flick(xSpeed: 5, ySpeed: 20)) + } + func testSessionSource() throws { let mockWebDriver: MockWebDriver = MockWebDriver() let session = Session(webDriver: mockWebDriver, existingId: "mySession")