From e358eded5ed69a6c2293c2a8ea9c5322685bef24 Mon Sep 17 00:00:00 2001 From: Squidonomics Date: Sun, 3 Dec 2023 22:28:14 -0800 Subject: [PATCH 1/5] feat: Touch methods --- Docs/SupportedAPIs.md | 4 +- Sources/WebDriver/Element.swift | 37 ++++++++++++++ Sources/WebDriver/Requests.swift | 49 +++++++++++++++++++ Sources/WebDriver/Session.swift | 8 +++ .../UnitTests/APIToRequestMappingTests.swift | 24 +++++++++ 5 files changed, 120 insertions(+), 2 deletions(-) diff --git a/Docs/SupportedAPIs.md b/Docs/SupportedAPIs.md index 1e6c987..5401460 100644 --- a/Docs/SupportedAPIs.md +++ b/Docs/SupportedAPIs.md @@ -48,9 +48,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.precisionFlick()`| | 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..090ca90 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, element: String) 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 precisionFlick(retryTimeout: TimeInterval? = nil, element: String, xOffset: Int, yOffset: Int, speed: Int) throws { + let request = Requests.SessionTouchFlickExact(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 0351bc0..831ad48 100644 --- a/Sources/WebDriver/Requests.swift +++ b/Sources/WebDriver/Requests.swift @@ -524,6 +524,55 @@ 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 SessionTouchFlickExact: Request { + public var session: String + public var element: String + public var xOffset: Int + public var yOffset: Int + public var speed: Int + + 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: Int + public var yOffset: Int + public var speed: Int + } + } + + // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidtouchflick-1 + public struct SessionTouchFlick: Request { + public var session: String + public var xSpeed: Int + public var ySpeed: Int + + 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: Int + public var ySpeed: Int + } + } + // 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 22bd368..76a5bf7 100644 --- a/Sources/WebDriver/Session.swift +++ b/Sources/WebDriver/Session.swift @@ -214,6 +214,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: Int, ySpeed: Int) 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 a674dd4..1c9a372 100644 --- a/Tests/UnitTests/APIToRequestMappingTests.swift +++ b/Tests/UnitTests/APIToRequestMappingTests.swift @@ -199,4 +199,28 @@ class APIToRequestMappingTests: XCTestCase { } XCTAssert(try session.size(window: "myWindow") == (width: 500, height: 500)) } + + 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/scroll", method: .post) + try element.doubleClick(element: element) + } + + 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) + try element.precisionFlick(element: element, xOffset: 5, yOffset: 20, speed: 2003) + } + + func testSessionFlick() 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) + try session.flick(xSpeed: 5, ySpeed: 20) + } } From ab70b57d525693ed781dd0f3581907952e7f984e Mon Sep 17 00:00:00 2001 From: Squidonomics Date: Sun, 3 Dec 2023 23:39:11 -0800 Subject: [PATCH 2/5] fix: Failing tests --- Tests/UnitTests/APIToRequestMappingTests.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Tests/UnitTests/APIToRequestMappingTests.swift b/Tests/UnitTests/APIToRequestMappingTests.swift index 1c9a372..43a8d31 100644 --- a/Tests/UnitTests/APIToRequestMappingTests.swift +++ b/Tests/UnitTests/APIToRequestMappingTests.swift @@ -205,7 +205,7 @@ class APIToRequestMappingTests: XCTestCase { let session = Session(webDriver: mockWebDriver, existingId: "mySession") let element = Element(session: session, id: "myElement") mockWebDriver.expect(path: "session/mySession/touch/scroll", method: .post) - try element.doubleClick(element: element) + XCTAssertNotNil(try element.doubleClick(element: "myElement")) } func testElementFlick() throws { @@ -213,14 +213,13 @@ class APIToRequestMappingTests: XCTestCase { let session = Session(webDriver: mockWebDriver, existingId: "mySession") let element = Element(session: session, id: "myElement") mockWebDriver.expect(path: "session/mySession/touch/flick", method: .post) - try element.precisionFlick(element: element, xOffset: 5, yOffset: 20, speed: 2003) + XCTAssertNotNil(try element.precisionFlick(element: "myElement", xOffset: 5, yOffset: 20, speed: 2003)) } func testSessionFlick() 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) - try session.flick(xSpeed: 5, ySpeed: 20) + XCTAssertNotNil(try session.flick(xSpeed: 5, ySpeed: 20)) } } From ceeb7c88ba8e0faef00b8ff1e09e07d6b95ca56b Mon Sep 17 00:00:00 2001 From: Squidonomics Date: Sun, 3 Dec 2023 23:42:57 -0800 Subject: [PATCH 3/5] fix: Failing tests --- Tests/UnitTests/APIToRequestMappingTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/UnitTests/APIToRequestMappingTests.swift b/Tests/UnitTests/APIToRequestMappingTests.swift index 43a8d31..404d347 100644 --- a/Tests/UnitTests/APIToRequestMappingTests.swift +++ b/Tests/UnitTests/APIToRequestMappingTests.swift @@ -204,7 +204,7 @@ class APIToRequestMappingTests: XCTestCase { let mockWebDriver: MockWebDriver = MockWebDriver() let session = Session(webDriver: mockWebDriver, existingId: "mySession") let element = Element(session: session, id: "myElement") - mockWebDriver.expect(path: "session/mySession/touch/scroll", method: .post) + mockWebDriver.expect(path: "session/mySession/touch/doubleclick", method: .post) XCTAssertNotNil(try element.doubleClick(element: "myElement")) } From 2eafd3f34fdd88708396519b78953815e6ddd96b Mon Sep 17 00:00:00 2001 From: Squidonomics Date: Sat, 24 Feb 2024 12:26:45 -0500 Subject: [PATCH 4/5] fix: Address pr comments --- Sources/WebDriver/Element.swift | 6 +-- Sources/WebDriver/Requests.swift | 41 ++++++++++--------- Sources/WebDriver/Session.swift | 2 +- .../UnitTests/APIToRequestMappingTests.swift | 3 +- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/Sources/WebDriver/Element.swift b/Sources/WebDriver/Element.swift index 090ca90..01b1d61 100644 --- a/Sources/WebDriver/Element.swift +++ b/Sources/WebDriver/Element.swift @@ -86,7 +86,7 @@ public struct Element { } /// Double clicks an element by id. - public func doubleClick(retryTimeout: TimeInterval? = nil, element: String) throws { + public func doubleClick(element: String, retryTimeout: TimeInterval? = nil) throws { let request = Requests.SessionTouchDoubleClick(session: session.id, element: id) let result = try poll(timeout: retryTimeout ?? session.defaultRetryTimeout) { do { @@ -107,8 +107,8 @@ public struct Element { /// - 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 precisionFlick(retryTimeout: TimeInterval? = nil, element: String, xOffset: Int, yOffset: Int, speed: Int) throws { - let request = Requests.SessionTouchFlickExact(session: session.id, element: id, xOffset: xOffset, yOffset: yOffset, speed: speed) + public func flick(element: String, 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. diff --git a/Sources/WebDriver/Requests.swift b/Sources/WebDriver/Requests.swift index 18a95f7..30e0ab8 100644 --- a/Sources/WebDriver/Requests.swift +++ b/Sources/WebDriver/Requests.swift @@ -547,8 +547,8 @@ public enum Requests { 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 var method: HTTPMethod { .post } + public var body: Body { .init(element: element) } public struct Body: Codable { public var element: String @@ -556,45 +556,46 @@ public enum Requests { } // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidtouchflick - public struct SessionTouchFlickExact: Request { + public struct SessionTouchFlickElement: Request { public var session: String public var element: String - public var xOffset: Int - public var yOffset: Int - public var speed: Int + 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 var method: HTTPMethod { .post } + public var body: Body { .init(xOffset: xOffset, yOffset: yOffset, speed: speed) } public struct Body: Codable { - public var xOffset: Int - public var yOffset: Int - public var speed: Int + public var xOffset: Double + public var yOffset: Double + public var speed: Double } } // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidtouchflick-1 public struct SessionTouchFlick: Request { public var session: String - public var xSpeed: Int - public var ySpeed: Int - + 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 var method: HTTPMethod { .post } + public var body: Body { .init(xSpeed: xSpeed, ySpeed: ySpeed) } + public struct Body: Codable { - public var xSpeed: Int - public var ySpeed: Int + public var xSpeed: Double + public var ySpeed: Double } + } // 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 var method: HTTPMethod { .get } public typealias Response = ResponseWithValue diff --git a/Sources/WebDriver/Session.swift b/Sources/WebDriver/Session.swift index 1935e3a..03ab588 100644 --- a/Sources/WebDriver/Session.swift +++ b/Sources/WebDriver/Session.swift @@ -222,7 +222,7 @@ public class Session { /// - 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: Int, ySpeed: Int) throws { + public func flick(xSpeed: Double, ySpeed: Double) throws { try webDriver.send(Requests.SessionTouchFlick(session: id, xSpeed: xSpeed, ySpeed: ySpeed)) } diff --git a/Tests/UnitTests/APIToRequestMappingTests.swift b/Tests/UnitTests/APIToRequestMappingTests.swift index 1e09044..1ec6da2 100644 --- a/Tests/UnitTests/APIToRequestMappingTests.swift +++ b/Tests/UnitTests/APIToRequestMappingTests.swift @@ -228,7 +228,7 @@ class APIToRequestMappingTests: XCTestCase { 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.precisionFlick(element: "myElement", xOffset: 5, yOffset: 20, speed: 2003)) + XCTAssertNotNil(try element.flick(element: "myElement", xOffset: 5, yOffset: 20, speed: 2003)) } func testSessionFlick() throws { @@ -236,6 +236,7 @@ class APIToRequestMappingTests: XCTestCase { 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() From 68f53dec69996ff7d89ac7a9130490cc98c382eb Mon Sep 17 00:00:00 2001 From: Squidonomics Date: Tue, 26 Mar 2024 20:31:44 -0400 Subject: [PATCH 5/5] fix: Codingkey touch request remove element id --- Docs/SupportedAPIs.md | 2 +- Sources/WebDriver/Element.swift | 4 ++-- Sources/WebDriver/Requests.swift | 11 +++++++++++ Tests/UnitTests/APIToRequestMappingTests.swift | 4 ++-- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Docs/SupportedAPIs.md b/Docs/SupportedAPIs.md index 69614d7..c22e1ac 100644 --- a/Docs/SupportedAPIs.md +++ b/Docs/SupportedAPIs.md @@ -53,7 +53,7 @@ Contributions to expand support to unimplemented functionality are always welcom | POST | `/session/:sessionId/touch/click` | Supported | `Element.touchClick()`| | POST | `/session/:sessionId/touch/doubleclick` | Supported | `Element.doubleClick()`| | POST | `/session/:sessionId/touch/down` | Supported | `Session.touchDown()`| -| POST | `/session/:sessionId/touch/flick` | Supported | `Session.flick()`, `Element.precisionFlick()`| +| 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 01b1d61..22f2b94 100644 --- a/Sources/WebDriver/Element.swift +++ b/Sources/WebDriver/Element.swift @@ -86,7 +86,7 @@ public struct Element { } /// Double clicks an element by id. - public func doubleClick(element: String, retryTimeout: TimeInterval? = nil) throws { + 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 { @@ -107,7 +107,7 @@ public struct Element { /// - 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(element: String, xOffset: Double, yOffset: Double, speed: Double, retryTimeout: TimeInterval? = nil) throws { + 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 { diff --git a/Sources/WebDriver/Requests.swift b/Sources/WebDriver/Requests.swift index 5badb0e..9013ab4 100644 --- a/Sources/WebDriver/Requests.swift +++ b/Sources/WebDriver/Requests.swift @@ -571,6 +571,12 @@ public enum Requests { 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" + } } } @@ -587,6 +593,11 @@ public enum Requests { public struct Body: Codable { public var xSpeed: Double public var ySpeed: Double + + private enum CodingKeys: String, CodingKey { + case xSpeed = "xspeed" + case ySpeed = "yspeed" + } } } diff --git a/Tests/UnitTests/APIToRequestMappingTests.swift b/Tests/UnitTests/APIToRequestMappingTests.swift index 290b012..783f7f8 100644 --- a/Tests/UnitTests/APIToRequestMappingTests.swift +++ b/Tests/UnitTests/APIToRequestMappingTests.swift @@ -294,7 +294,7 @@ class APIToRequestMappingTests: XCTestCase { 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(element: "myElement")) + XCTAssertNotNil(try element.doubleClick()) } func testElementFlick() throws { @@ -302,7 +302,7 @@ class APIToRequestMappingTests: XCTestCase { 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(element: "myElement", xOffset: 5, yOffset: 20, speed: 2003)) + XCTAssertNotNil(try element.flick(xOffset: 5, yOffset: 20, speed: 2003)) } func testSessionFlick() throws {