This repository was archived by the owner on Feb 24, 2025. It is now read-only.
Repository navigation
[Draft] shared web tests #3933
Draft
jonathanKingston
wants to merge
28
commits into
main
Choose a base branch
from
jkt/shared-web-tests
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
[Draft] shared web tests #3933
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
97c7254
Big ol backup
jonathanKingston 8b76ca3
Backup of existing work
jonathanKingston 39b2b03
Merge remote-tracking branch 'origin/main' into jkt/shared-web-tests
jonathanKingston 7803231
Use launch args and logging
jonathanKingston 7c02f28
Merge remote-tracking branch 'origin/main' into jkt/shared-web-tests
jonathanKingston f8ec2b4
Remove maestro specific changes
jonathanKingston 6549f46
Add github workflow
jonathanKingston 9051f24
Remove maestro specific setup
jonathanKingston 8467795
Reorder action
jonathanKingston f3ab4cc
Move changes to extensions
jonathanKingston 3c11508
Separate out automation steps into their own methods
jonathanKingston 7043bf3
Merge remote-tracking branch 'origin/main' into jkt/shared-web-tests
jonathanKingston 463ab99
Remove debug if statements
jonathanKingston d46f947
Remove force try in automation server
jonathanKingston 8f82b7d
Rename startAutomationServer to startAutomationServerIfNeeded
jonathanKingston f05c8d7
Remove making controller public
jonathanKingston 93ac4df
Make AutomationServer final
jonathanKingston 7b00a95
Remove stray commented out global connection handler for Automation
jonathanKingston cbcd86f
Move currentURL method into direct access within the server
jonathanKingston e76cd77
Reduce nesting in AutomationServer
jonathanKingston 4313f26
Return early by using guards for error states
jonathanKingston 2fd77ec
Switch object identifiers to use tabModel.uid
jonathanKingston 8f4a97f
Merge remote-tracking branch 'origin/main' into jkt/shared-web-tests
jonathanKingston 784ddb5
Merge remote-tracking branch 'origin/main' into jkt/shared-web-tests
jonathanKingston 6d71981
Automation fixes
jonathanKingston ef7df0c
Get mainViewController in automation server
jonathanKingston 2b558e9
Simplify configuration of tests
jonathanKingston af83c26
Move testing steps to npm
jonathanKingston File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Use launch args and logging
- Loading branch information
commit 78032318107969076a77e7ddc91ff399324542c7
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,28 +19,15 @@ | |
| import Foundation | ||
| import Network | ||
|
|
||
| /* | ||
| // WebDriver BiDi automation server | ||
| class AutomationServerBidi { | ||
| let listener: NWListener | ||
| let main: MainViewController | ||
|
|
||
| init(main: MainViewController, port: Int?) { | ||
| self.main = main | ||
| listener = try! NWListener(using: .tcp, on: NWEndpoint.Port(integerLiteral: UInt16(port ?? 8786))) | ||
| listener.newConnectionHandler = handleConnection | ||
| // listener.start(queue: .global()) | ||
| listener.start(queue: .main) | ||
| } | ||
| extension Logger { | ||
| static var automationServer = { Logger(subsystem: Bundle.main.bundleIdentifier ?? "DuckDuckGo", category: "Automation Server") }() | ||
| } | ||
| */ | ||
|
|
||
|
|
||
| struct Log: TextOutputStream { | ||
|
|
||
| func write(_ string: String) { | ||
| let fm = FileManager.default | ||
| let log = fm.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("log.txt") | ||
| let log = fm.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("log-automation.txt") | ||
| if let handle = try? FileHandle(forWritingTo: log) { | ||
| handle.seekToEndOfFile() | ||
| handle.write(string.data(using: .utf8)!) | ||
|
|
@@ -54,16 +41,18 @@ struct Log: TextOutputStream { | |
| class AutomationServer { | ||
|
jonathanKingston marked this conversation as resolved.
Outdated
|
||
| let listener: NWListener | ||
| let main: MainViewController | ||
| var logger: Log | ||
|
|
||
| init(main: MainViewController, port: Int?) { | ||
| self.logger = Log() | ||
|
|
||
| var port = port ?? 8786 | ||
| self.main = main | ||
| listener = try! NWListener(using: .tcp, on: NWEndpoint.Port(integerLiteral: UInt16(port ?? 8786))) | ||
| var log = Log() | ||
| print("Starting automation server on port \(port)", to: &log) | ||
| print("Bundle: \(Bundle.main.bundleIdentifier)", to: &log) | ||
| listener = try! NWListener(using: .tcp, on: NWEndpoint.Port(integerLiteral: UInt16(port))) | ||
| listener.newConnectionHandler = handleConnection | ||
| // listener.start(queue: .global()) | ||
| listener.start(queue: .main) | ||
| // Output server started | ||
| Logger.automationServer.info("Automation server started on port \(port)") | ||
| } | ||
|
|
||
| @MainActor | ||
|
|
@@ -76,28 +65,28 @@ class AutomationServer { | |
| case .ready: | ||
| break // Connection is valid, continue | ||
| case .cancelled, .failed: | ||
| print("Connection is no longer valid \(connection.state) \(error) \(content).", to: &self.logger) | ||
| print("Connection is no longer valid \(connection.state) \(String(describing: error)) \(String(describing: content)).") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please replace this print with logging (other places too) |
||
| return | ||
| default: | ||
| print("Connection is in state \(connection.state).", to: &self.logger) | ||
| print("Connection is in state \(connection.state).") | ||
| return | ||
| } | ||
| print("Received request! \(content) \(isComplete) \(error)", to: &self.logger) | ||
| Logger.automationServer.info("Received request! \(String(describing: content)) \(isComplete) \(String(describing: error))") | ||
|
|
||
| if let error { | ||
| print("Error: \(error)", to: &self.logger) | ||
| Logger.automationServer.error("Error: \(error)") | ||
| return | ||
| } | ||
|
|
||
| if let content { | ||
| print("Handling content", to: &self.logger) | ||
| Logger.automationServer.info("Handling content") | ||
| Task { | ||
| await self.processContentWhenReady(connection: connection, content: content) | ||
| } | ||
| } | ||
|
|
||
| if !isComplete { | ||
| print("Handling not complete", to: &self.logger) | ||
| Logger.automationServer.info("Handling not complete") | ||
| self.receive(from: connection) | ||
| } | ||
| } | ||
|
|
@@ -112,17 +101,17 @@ class AutomationServer { | |
| } | ||
|
|
||
| // Proceed when loading is complete | ||
| print("Handling content", to: &self.logger) | ||
| Logger.automationServer.info("Handling content") | ||
| self.handleConnection(connection, content) | ||
| } | ||
|
|
||
| @MainActor | ||
| func handleConnection(_ connection: NWConnection, _ content: Data) { | ||
| print("Handling request!", to: &self.logger) | ||
| Logger.automationServer.info("Handling request!") | ||
| let stringContent = String(decoding: content, as: UTF8.self) | ||
| // Log first line of string: | ||
| if let firstLine = stringContent.components(separatedBy: CharacterSet.newlines).first { | ||
| print(firstLine, to: &self.logger) | ||
| Logger.automationServer.info("First line: \(firstLine)") | ||
| } | ||
|
|
||
| func getQueryStringParameter(url: String, param: String) -> String? { | ||
|
|
@@ -134,7 +123,7 @@ class AutomationServer { | |
| if #available(iOS 16.0, *) { | ||
|
jonathanKingston marked this conversation as resolved.
Outdated
|
||
| let path = /^(GET|POST) (\/[^ ]*) HTTP/ | ||
| if let match = stringContent.firstMatch(of: path) { | ||
| print("Path: \(match.2)", to: &logger) | ||
| Logger.automationServer.info("Path: \(match.2)") | ||
| // Convert the path into a URL object | ||
| guard let url = URL(string: String(match.2)) else { | ||
| print("Invalid URL: \(match.2)") | ||
|
|
@@ -143,12 +132,11 @@ class AutomationServer { | |
| if url.path == "/navigate" { | ||
| let navigateUrlString = getQueryStringParameter(url: String(match.2), param: "url") ?? "" | ||
| let navigateUrl = URL(string: navigateUrlString)! | ||
| self.main.navigateTo(url: navigateUrl) | ||
| self.main.loadUrl(navigateUrl) | ||
| self.respond(on: connection, response: "done") | ||
| } else if url.path == "/execute" { | ||
| let script = getQueryStringParameter(url: String(match.2), param: "script") ?? "" | ||
| var args: [String: String] = [:] | ||
| print("Script: \(script)", to: &self.logger) | ||
| // json decode args | ||
| if let argsString = getQueryStringParameter(url: String(match.2), param: "args") { | ||
| if let argsData = argsString.data(using: .utf8) { | ||
|
|
@@ -192,15 +180,15 @@ class AutomationServer { | |
| self.respond(on: connection, response: "{\"success\":true}") | ||
| } else if url.path == "/switchToWindow" { | ||
| if let handleString = getQueryStringParameter(url: String(match.2), param: "handle") { | ||
| print("Switch to window \(handleString)", to: &logger) | ||
| Logger.automationServer.info("Switch to window \(handleString)") | ||
| let tabToSelect: TabViewController? = nil | ||
| if let tabIndex = self.main.tabManager.model.tabs.firstIndex(where: { tab in | ||
| guard let tabView = self.main.tabManager.controller(for: tab) else { | ||
| return false | ||
| } | ||
| return String(UInt(bitPattern: ObjectIdentifier(tabView))) == handleString | ||
| }) { | ||
| print("found tab \(tabIndex)", to: &logger) | ||
| Logger.automationServer.info("found tab \(tabIndex)") | ||
| self.main.tabManager.select(tabAt: tabIndex) | ||
| self.respond(on: connection, response: "{\"success\":true}") | ||
| } else { | ||
|
|
@@ -243,9 +231,9 @@ class AutomationServer { | |
| } | ||
|
|
||
| func executeScript(_ script: String, args: [String: Any], on connection: NWConnection) async { | ||
| print("Going to execute script: \(script)", to: &self.logger) | ||
| Logger.automationServer.info("Going to execute script: \(script)") | ||
| var result = await main.executeScript(script, args: args) | ||
| print("Have result to execute script: \(result)", to: &self.logger) | ||
| Logger.automationServer.info("Have result to execute script: \(String(describing: result))") | ||
| guard var result else { | ||
| return | ||
| } | ||
|
|
@@ -259,16 +247,20 @@ class AutomationServer { | |
| // Try to encode the value to JSON | ||
| let encoder = JSONEncoder() | ||
| encoder.outputFormatting = .prettyPrinted | ||
| Logger.automationServer.info("Have success value to execute script: \(String(describing: value))") | ||
|
|
||
| // Serialize the value to JSON if possible | ||
| if JSONSerialization.isValidJSONObject(value) { | ||
| if value == nil { | ||
| jsonString = "{}" | ||
| } else if JSONSerialization.isValidJSONObject(value) { | ||
| do { | ||
| let jsonData = try JSONSerialization.data(withJSONObject: value, options: [.prettyPrinted]) | ||
| jsonString = String(data: jsonData, encoding: .utf8) ?? "{}" | ||
| } catch { | ||
| jsonString = "{\"error\": \"Failed to serialize value: \(error.localizedDescription)\"}" | ||
| } | ||
| } else { | ||
| Logger.automationServer.info("Have value that can't be encoded: \(String(describing: value))") | ||
| jsonString = "{\"error\": \"Value is not a valid JSON object\"}" | ||
| } | ||
|
|
||
|
|
@@ -306,14 +298,14 @@ class AutomationServer { | |
| content: response.data(using: .utf8), | ||
| completion: .contentProcessed({ error in | ||
| if let error = error { | ||
| print("Error sending response: \(error)", to: &self.logger) | ||
| Logger.automationServer.error("Error sending response: \(error)") | ||
| } | ||
| connection.cancel() | ||
| }) | ||
| ) | ||
| } | ||
| } catch { | ||
| print("Got error encoding JSON: \(error)", to: &logger) | ||
| Logger.automationServer.error("Got error encoding JSON: \(error)") | ||
| } | ||
| } | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.


Uh oh!
There was an error while loading. Please reload this page.