From 0d1ed6da95a6d10ae1d8b7630e61d3ed6611036d Mon Sep 17 00:00:00 2001 From: William Date: Tue, 12 Aug 2025 16:36:55 -0600 Subject: [PATCH] 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'" },