From 0d1ed6da95a6d10ae1d8b7630e61d3ed6611036d Mon Sep 17 00:00:00 2001 From: William Date: Tue, 12 Aug 2025 16:36:55 -0600 Subject: [PATCH 1/2] feat(ChromeDriver): implemented `setAttribute` method written integration tests, tests passed as expected --- .../API/Request/DevTools/AnyEncodable.swift | 16 +++++++++++ .../DevTools/PostExecuteAsyncRequest.swift | 8 +++--- .../DevTools/PostExecuteSyncRequest.swift | 6 ++--- Sources/SwiftWebDriver/WebDriver.swift | 7 ++++- .../WebDrivers/Chrome/ChromeDriver.swift | 17 +++++++++--- .../SwiftWebDriver/WebDrivers/Driver.swift | 4 ++- ...meDriverSetAttributeIntegrationTests.swift | 27 +++++++++++++++++++ package.json | 8 ++++++ 8 files changed, 80 insertions(+), 13 deletions(-) create mode 100644 Sources/SwiftWebDriver/API/Request/DevTools/AnyEncodable.swift create mode 100644 Tests/SwiftWebDriverIntegrationTests/ChromeDriver/Element/ChromeDriverSetAttributeIntegrationTests.swift diff --git a/Sources/SwiftWebDriver/API/Request/DevTools/AnyEncodable.swift b/Sources/SwiftWebDriver/API/Request/DevTools/AnyEncodable.swift new file mode 100644 index 0000000..b705110 --- /dev/null +++ b/Sources/SwiftWebDriver/API/Request/DevTools/AnyEncodable.swift @@ -0,0 +1,16 @@ +// AnyEncodable.swift +// Copyright (c) 2025 GetAutomaApp +// All source code and related assets are the property of GetAutomaApp. +// All rights reserved. + +public struct AnyEncodable: Encodable { + private let encodeFunc: (Encoder) throws -> Void + + init(_ value: some Encodable) { + encodeFunc = value.encode + } + + public func encode(to encoder: Encoder) throws { + try encodeFunc(encoder) + } +} diff --git a/Sources/SwiftWebDriver/API/Request/DevTools/PostExecuteAsyncRequest.swift b/Sources/SwiftWebDriver/API/Request/DevTools/PostExecuteAsyncRequest.swift index 383ab43..d0d8fe8 100644 --- a/Sources/SwiftWebDriver/API/Request/DevTools/PostExecuteAsyncRequest.swift +++ b/Sources/SwiftWebDriver/API/Request/DevTools/PostExecuteAsyncRequest.swift @@ -39,11 +39,9 @@ internal struct PostExecuteAsyncRequest: RequestType { return .data(data) } -} -internal extension PostExecuteAsyncRequest { - struct RequestBody: Codable { - public let script: String - public let args: [String] + struct RequestBody: Encodable { + let script: String + let args: [AnyEncodable] } } diff --git a/Sources/SwiftWebDriver/API/Request/DevTools/PostExecuteSyncRequest.swift b/Sources/SwiftWebDriver/API/Request/DevTools/PostExecuteSyncRequest.swift index 5f875b6..acb2667 100644 --- a/Sources/SwiftWebDriver/API/Request/DevTools/PostExecuteSyncRequest.swift +++ b/Sources/SwiftWebDriver/API/Request/DevTools/PostExecuteSyncRequest.swift @@ -41,8 +41,8 @@ internal struct PostExecuteSyncRequest: RequestType { } internal extension PostExecuteSyncRequest { - struct RequestBody: Codable { - public let script: String - public let args: [String] + struct RequestBody: Encodable { + let script: String + let args: [AnyEncodable] } } diff --git a/Sources/SwiftWebDriver/WebDriver.swift b/Sources/SwiftWebDriver/WebDriver.swift index d94b860..f35eb9a 100644 --- a/Sources/SwiftWebDriver/WebDriver.swift +++ b/Sources/SwiftWebDriver/WebDriver.swift @@ -120,7 +120,7 @@ public class WebDriver { @discardableResult public func execute( _ script: String, - args: [String] = [], + args: [AnyEncodable] = [], type: DevToolTypes.JavascriptExecutionTypes = .sync ) async throws -> PostExecuteResponse { try await driver.execute(script, args: args, type: type) @@ -131,4 +131,9 @@ public class WebDriver { public func getActiveElement() async throws -> Element { try await driver.getActiveElement() } + + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) + func setAttribute(element: Element, attributeName: String, newValue: String) async throws { + try await driver.setAttribute(element: element, attributeName: attributeName, newValue: newValue) + } } diff --git a/Sources/SwiftWebDriver/WebDrivers/Chrome/ChromeDriver.swift b/Sources/SwiftWebDriver/WebDrivers/Chrome/ChromeDriver.swift index 4df3410..1d27aa1 100644 --- a/Sources/SwiftWebDriver/WebDrivers/Chrome/ChromeDriver.swift +++ b/Sources/SwiftWebDriver/WebDrivers/Chrome/ChromeDriver.swift @@ -247,7 +247,7 @@ public class ChromeDriver: Driver { } } - private func executeJavascriptSync(_ script: String, args: [String]) async throws -> PostExecuteResponse { + private func executeJavascriptSync(_ script: String, args: [AnyEncodable]) async throws -> PostExecuteResponse { guard let sessionId else { throw WebDriverError.sessionIdIsNil } @@ -261,7 +261,7 @@ public class ChromeDriver: Driver { return try await client.request(request) } - private func executeJavascriptAsync(_ script: String, args: [String]) async throws -> PostExecuteResponse { + private func executeJavascriptAsync(_ script: String, args: [AnyEncodable]) async throws -> PostExecuteResponse { guard let sessionId else { throw WebDriverError.sessionIdIsNil } @@ -279,7 +279,7 @@ public class ChromeDriver: Driver { @discardableResult public func execute( _ script: String, - args: [String], + args: [AnyEncodable], type: DevToolTypes.JavascriptExecutionTypes ) async throws -> PostExecuteResponse { try await type == .sync ? @@ -301,6 +301,17 @@ public class ChromeDriver: Driver { return Element(baseURL: url, sessionId: sessionId, elementId: response.elementId) } + public func setAttribute(element: Element, attributeName: String, newValue: String) async throws { + let script = "arguments[0].setAttribute(arguments[1], arguments[2]);" + + let args: [AnyEncodable] = [ + AnyEncodable(["element-6066-11e4-a52e-4f735466cecf": element.elementId]), + AnyEncodable(attributeName), + AnyEncodable(newValue) + ] + try await execute(script, args: args, type: .sync) + } + deinit { let url = url let sessionId = sessionId diff --git a/Sources/SwiftWebDriver/WebDrivers/Driver.swift b/Sources/SwiftWebDriver/WebDrivers/Driver.swift index 66781b6..87d6637 100644 --- a/Sources/SwiftWebDriver/WebDrivers/Driver.swift +++ b/Sources/SwiftWebDriver/WebDrivers/Driver.swift @@ -44,9 +44,11 @@ public protocol Driver: FindElementProtocol { func execute( _ script: String, - args: [String], + args: [AnyEncodable], type: DevToolTypes.JavascriptExecutionTypes ) async throws -> PostExecuteResponse func getActiveElement() async throws -> Element + + func setAttribute(element: Element, attributeName: String, newValue: String) async throws } diff --git a/Tests/SwiftWebDriverIntegrationTests/ChromeDriver/Element/ChromeDriverSetAttributeIntegrationTests.swift b/Tests/SwiftWebDriverIntegrationTests/ChromeDriver/Element/ChromeDriverSetAttributeIntegrationTests.swift new file mode 100644 index 0000000..ba63848 --- /dev/null +++ b/Tests/SwiftWebDriverIntegrationTests/ChromeDriver/Element/ChromeDriverSetAttributeIntegrationTests.swift @@ -0,0 +1,27 @@ +// ChromeDriverSetAttributeIntegrationTests.swift +// Copyright (c) 2025 GetAutomaApp +// All source code and related assets are the property of GetAutomaApp. +// All rights reserved. + +@testable import SwiftWebDriver +import Testing + +@Suite("Chrome Driver Set Attribute", .serialized) +internal class ChromeDriverSetAttributeIntegrationTests: ChromeDriverTest { + @Test("Set Attribute") + public func setAttribute() async throws { + page = "elementHandleTestPage.html" + try await driver.navigateTo(urlString: testPageURL.absoluteString) + + let element = try await driver.findElement(.css(.id("attribute"))) + let newIdentifier = "newidentifier" + try await driver.setAttribute(element: element, attributeName: "id", newValue: newIdentifier) + + let elementId = try await element.attribute(name: "id") + #expect(elementId == newIdentifier) + } + + deinit { + // Add deinit logic here + } +} diff --git a/package.json b/package.json index 5f3b646..86525e1 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,14 @@ "install:all": "npx npm-run-all install:swiftformat install:swiftlint config", "install:swiftformat": "brew install swiftformat", "install:swiftlint": "brew install swiftlint", + "compose:up": "npm run compose -- up -d", + "compose:build": "npm run compose -- build", + "compose:build:no-cache": "npm run compose -- build --no-cache", + "compose:build-and-up": "npx npm-run-all --sequential compose:build compose:up", + "compose:build-no-cache-and-up": "npx npm-run-all --sequential compose:build:no-cache compose:up", + "compose:down": "npm run compose -- down", + "compose:ps": "npm run compose -- ps -a", + "compose": "docker compose", "config": "./.dotfiles/config.sh", "update:submodules": "git submodule foreach --recursive 'branch=$(git remote show origin | awk \"/HEAD branch/ {print \\$NF}\"); git checkout $branch && git pull origin $branch' && CHANGED=$(git status --porcelain | grep '^ M \\.dotfiles' || true) && if [ -n \"$CHANGED\" ]; then npm run config; fi && git add -A && git commit -m \"chore: update submodules\" || echo 'No changes to commit'" }, From 4d4f26d33f88d1d0641feb12b2547c11d60f9944 Mon Sep 17 00:00:00 2001 From: William Date: Wed, 13 Aug 2025 09:24:37 -0600 Subject: [PATCH 2/2] feat(ChromeDriver): implemented `getProperty()` method, to get the live value of an element property by using JavaScript Selenium WebDriver API does note have a way to get the live value of an attribute, like an updated value of an input element. The only way to get the live value would be to use JavaScript. That is what the `getProperty()` method does. Integration tests written for this method, and passed as expected. --- Sources/SwiftWebDriver/WebDriver.swift | 9 +++--- .../WebDrivers/Chrome/ChromeDriver.swift | 13 ++++++-- .../SwiftWebDriver/WebDrivers/Driver.swift | 5 ++- ...omeDriverGetPropertyIntegrationTests.swift | 32 +++++++++++++++++++ 4 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 Tests/SwiftWebDriverIntegrationTests/ChromeDriver/Element/ChromeDriverGetPropertyIntegrationTests.swift diff --git a/Sources/SwiftWebDriver/WebDriver.swift b/Sources/SwiftWebDriver/WebDriver.swift index f35eb9a..3364eeb 100644 --- a/Sources/SwiftWebDriver/WebDriver.swift +++ b/Sources/SwiftWebDriver/WebDriver.swift @@ -2,9 +2,6 @@ // Copyright (c) 2025 GetAutomaApp // All source code and related assets are the property of GetAutomaApp. // All rights reserved. -// -// This package is freely distributable under the MIT license. -// This Package is a modified fork of https://github.com/ashi-psn/SwiftWebDriver. import Foundation import NIOCore @@ -133,7 +130,11 @@ public class WebDriver { } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func setAttribute(element: Element, attributeName: String, newValue: String) async throws { + public func setAttribute(element: Element, attributeName: String, newValue: String) async throws { try await driver.setAttribute(element: element, attributeName: attributeName, newValue: newValue) } + + public func getProperty(element: Element, property: String) async throws -> PostExecuteResponse { + try await driver.getProperty(element: element, property: property) + } } diff --git a/Sources/SwiftWebDriver/WebDrivers/Chrome/ChromeDriver.swift b/Sources/SwiftWebDriver/WebDrivers/Chrome/ChromeDriver.swift index 1d27aa1..6ad2ac3 100644 --- a/Sources/SwiftWebDriver/WebDrivers/Chrome/ChromeDriver.swift +++ b/Sources/SwiftWebDriver/WebDrivers/Chrome/ChromeDriver.swift @@ -2,9 +2,6 @@ // Copyright (c) 2025 GetAutomaApp // All source code and related assets are the property of GetAutomaApp. // All rights reserved. -// -// This package is freely distributable under the MIT license. -// This Package is a modified fork of https://github.com/ashi-psn/SwiftWebDriver. import AsyncHTTPClient import Foundation @@ -312,6 +309,16 @@ public class ChromeDriver: Driver { try await execute(script, args: args, type: .sync) } + public func getProperty(element: Element, property: String) async throws -> PostExecuteResponse { + let script = "return arguments[0][arguments[1]];" + + let args: [AnyEncodable] = [ + AnyEncodable(["element-6066-11e4-a52e-4f735466cecf": element.elementId]), + AnyEncodable(property) + ] + return try await execute(script, args: args, type: .sync) + } + deinit { let url = url let sessionId = sessionId diff --git a/Sources/SwiftWebDriver/WebDrivers/Driver.swift b/Sources/SwiftWebDriver/WebDrivers/Driver.swift index 87d6637..e8e1069 100644 --- a/Sources/SwiftWebDriver/WebDrivers/Driver.swift +++ b/Sources/SwiftWebDriver/WebDrivers/Driver.swift @@ -2,9 +2,6 @@ // Copyright (c) 2025 GetAutomaApp // All source code and related assets are the property of GetAutomaApp. // All rights reserved. -// -// This package is freely distributable under the MIT license. -// This Package is a modified fork of https://github.com/ashi-psn/SwiftWebDriver. import AsyncHTTPClient import Foundation @@ -51,4 +48,6 @@ public protocol Driver: FindElementProtocol { func getActiveElement() async throws -> Element func setAttribute(element: Element, attributeName: String, newValue: String) async throws + + func getProperty(element: Element, property: String) async throws -> PostExecuteResponse } diff --git a/Tests/SwiftWebDriverIntegrationTests/ChromeDriver/Element/ChromeDriverGetPropertyIntegrationTests.swift b/Tests/SwiftWebDriverIntegrationTests/ChromeDriver/Element/ChromeDriverGetPropertyIntegrationTests.swift new file mode 100644 index 0000000..0ad1f1c --- /dev/null +++ b/Tests/SwiftWebDriverIntegrationTests/ChromeDriver/Element/ChromeDriverGetPropertyIntegrationTests.swift @@ -0,0 +1,32 @@ +// ChromeDriverGetPropertyIntegrationTests.swift +// Copyright (c) 2025 GetAutomaApp +// All source code and related assets are the property of GetAutomaApp. +// All rights reserved. + +@testable import SwiftWebDriver +import Testing + +@Suite("Chrome Driver Get Property", .serialized) +internal class ChromeDriverGetPropertyIntegrationTests: ChromeDriverTest { + @Test("Get Property") + func getProperty() async throws { + page = "elementHandleTestPage.html" + try await driver.navigateTo(urlString: testPageURL.absoluteString) + let updatedElementValue = "NewElementValue" + try await driver.execute(""" + let element = document.getElementById('attribute') + element.value = '\(updatedElementValue)' + """) + let element = try await driver.findElement(.css(.id("attribute"))) + guard + let elementValue = try await driver.getProperty(element: element, property: "value").value?.stringValue + else { + #expect(Bool(false), "Could not convert element value to string value") + return + } + + #expect(elementValue == updatedElementValue) + } + + deinit {} +}