Skip to content

Conversation

@limbonaut
Copy link
Collaborator

@limbonaut limbonaut commented Dec 10, 2025

I tested it on iOS in Sauce Labs, and it should work on Android too. This PR also fixes some issues specific to iOS in Sauce Labs runner: splitting CLI arguments correctly and using correct Appium parameter for iOS.

These changes are sufficient to get E2E tests green on iOS in the Godot SDK.

@limbonaut limbonaut changed the title feat: Implement CopyDeviceitem for Sauce Labs feat: Implement CopyDeviceitem for Sauce Labs & fix issues Dec 10, 2025
@limbonaut limbonaut changed the title feat: Implement CopyDeviceitem for Sauce Labs & fix issues feat: Implement CopyDeviceItem for Sauce Labs & fix issues Dec 10, 2025
@limbonaut limbonaut marked this pull request as ready for review December 10, 2025 11:00
- Add optional LogFilePath parameter to Invoke-DeviceApp and RunApplication
- Implement log file retrieval with fallback to system logs in SauceLabsProvider
- Add method overloads to AdbProvider and XboxProvider for compatibility
- Reduce code footprint by consolidating log retrieval logic
- Maintain backward compatibility with existing provider implementations

Co-authored-by: Claude Sonnet <claude@anthropic.com>
@limbonaut limbonaut changed the title feat: Implement CopyDeviceItem for Sauce Labs & fix issues Sauce: Implement CopyDeviceItem, log file parameter for RunApplication & fix issues Dec 10, 2025
limbonaut and others added 2 commits December 10, 2025 12:48
- Replace method overloads with single method using optional LogFilePath parameter
- Update base DeviceProvider class to include optional parameter
- Simplify all provider implementations (SauceLabs, Adb, Xbox)
- Remove conditional logic from Invoke-DeviceApp - single method call
- Clean up code by eliminating method overload complexity

Co-authored-by: Claude Sonnet <claude@anthropic.com>
- Add optional LogFilePath parameter to match other providers
- Ensures consistent method signature across all provider implementations
- Completes the refactoring to use optional parameters instead of overloads

Co-authored-by: Claude Sonnet <claude@anthropic.com>
@limbonaut
Copy link
Collaborator Author

@vaind
Copy link
Contributor

vaind commented Dec 12, 2025

On it! We are reviewing the PR and will provide feedback shortly.
Reference ID: 1

Comment on lines +374 to +382

# Split the arguments string by spaces, but handle quoted strings (both single and double quotes)
$argTokens = [regex]::Matches($Arguments, '(\"[^\"]*\"|''[^'']*''|\S+)') | ForEach-Object { $_.Value.Trim('"', "'") }

foreach ($token in $argTokens) {
$argumentsArray += $token
}

$launchBody['arguments'] = $argumentsArray

This comment was marked as outdated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even better, how about we change $arguments param to [object] and let users pass string or array?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, [string[]] actually accepts both a string and an array of strings

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have multiple places where we represent arguments as a single string. I suppose I can refactor all of that in this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you shouldn't really need to change that. a single string works the same as an array of a a single string

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean that we accept a string as arguments in various API calls. Now if we change policy to accept arguments as an array, we still need arguments as a single string in some places. Converting from array of strings to a single string is not straightforward - requires proper escaping. One way or the other we'll have to deal with conversion.

Comment on lines +738 to +755
if ($Error.Exception.Message -match "500|Internal Server Error") {
$errorMsg += "`n`nTroubleshooting $($this.MobilePlatform) file access:"
$errorMsg += "`n- App Package/Bundle ID: '$($this.CurrentPackageName)'"
$errorMsg += "`n- Requested path: '$DevicePath'"

if ($this.MobilePlatform -eq 'iOS') {
try {
$appInfo = $this.CheckAppFileSharingCapability()
if ($appInfo.AllApps -and $appInfo.AllApps.Count -gt 0) {
$errorMsg += "`n- Available apps: $($appInfo.AllApps -join ', ')"
if ($appInfo.Found -and -not $appInfo.FileSharingEnabled) {
$errorMsg += "`n- App found but UIFileSharingEnabled=false"
}
}
} catch {
$errorMsg += "`n- Could not check app capabilities: $($_.Exception.Message)"
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the HandleCopyDeviceItemError method, the error handling tries to call CheckAppFileSharingCapability() to provide diagnostic information. However, if this method itself throws an exception (which it can on line 629 with 'No active SauceLabs session'), the exception is caught but the session existence should have been verified before attempting the CopyDeviceItem operation. Consider moving the session check to the beginning of CopyDeviceItem instead of relying on nested error handling.
Severity: MEDIUM

🤖 Prompt for AI Agent

Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: app-runner/Private/DeviceProviders/SauceLabsProvider.ps1#L738-L755

Potential issue: In the `HandleCopyDeviceItemError` method, the error handling tries to
call `CheckAppFileSharingCapability()` to provide diagnostic information. However, if
this method itself throws an exception (which it can on line 629 with 'No active
SauceLabs session'), the exception is caught but the session existence should have been
verified before attempting the CopyDeviceItem operation. Consider moving the session
check to the beginning of `CopyDeviceItem` instead of relying on nested error handling.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 1

Comment on lines 15 to 25
.PARAMETER LogFilePath
Optional path to a log file on the device to retrieve instead of using system logs (syslog/logcat).
Path format is platform-specific:
- iOS: Use bundle format like "@com.example.app:documents/logs/app.log"
- Android: Use absolute path like "/data/data/com.example.app/files/logs/app.log"
.EXAMPLE
Invoke-DeviceApp -ExecutablePath "MyGame.exe" -Arguments "--debug --level=1"
.EXAMPLE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new LogFilePath parameter documentation provides good examples for iOS and Android, but doesn't mention platform limitations. Since only SauceLabsProvider currently implements log file retrieval, calling Invoke-DeviceApp with LogFilePath on other platforms will silently ignore the parameter (due to the default null value). Consider adding a note in the documentation that this parameter is platform-specific, or consider warning the user if the parameter is provided but not supported by the current provider.
Severity: MEDIUM

🤖 Prompt for AI Agent

Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: app-runner/Public/Invoke-DeviceApp.ps1#L15-L25

Potential issue: The new `LogFilePath` parameter documentation provides good examples
for iOS and Android, but doesn't mention platform limitations. Since only
SauceLabsProvider currently implements log file retrieval, calling `Invoke-DeviceApp`
with `LogFilePath` on other platforms will silently ignore the parameter (due to the
default null value). Consider adding a note in the documentation that this parameter is
platform-specific, or consider warning the user if the parameter is provided but not
supported by the current provider.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 1

Found = $true
BundleId = $this.CurrentPackageName
FileSharingEnabled = [bool]$targetApp.UIFileSharingEnabled
Name = $targetApp.CFBundleDisplayName -or $targetApp.CFBundleName -or "Unknown"
Copy link
Contributor

@vaind vaind Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or doesn't work like a null-coallescing operator in Powershell so the current code returns $true instead of a name. Instead, you can do this:

Suggested change
Name = $targetApp.CFBundleDisplayName -or $targetApp.CFBundleName -or "Unknown"
Name = $targetApp.CFBundleDisplayName ?? $targetApp.CFBundleName ?? "Unknown"

but that could still return an empty string - "??" only works with nulls.
If it's possible these would be empty strings, you need to expand to if-else loop, for example like this:

Suggested change
Name = $targetApp.CFBundleDisplayName -or $targetApp.CFBundleName -or "Unknown"
Name = $(
if ($targetApp.CFBundleDisplayName) { $targetApp.CFBundleDisplayName }
elseif ($targetApp.CFBundleName) { $targetApp.CFBundleName }
else { "Unknown" }
)

Comment on lines +783 to +784
Write-Error $errorMsg
throw
Copy link
Contributor

@vaind vaind Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write-error doesn't just log. with our ErrorActionPreference, it's functionally equivalent to throwing. so the next line is never reached. You can use Write-Warning instead

Copy link
Contributor

@vaind vaind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've found a couple of bugs and a few improvement ideas

Updated device provider APIs to consistently accept arguments as string arrays rather than pre-formatted strings, improving type safety and eliminating argument parsing ambiguities. Added ConvertArgumentsToString method to DeviceProvider base class for consistent string conversion when needed by underlying tools.

Co-authored-by: Claude <claude@anthropic.com>
Comment on lines 374 to +379
if ($Arguments) {
# Appium 'mobile: launchApp' supports arguments?
# Or use 'appium:processArguments' capability during session creation?
# For now, we'll try to pass them if supported by the endpoint or warn.
$launchBody['arguments'] = $Arguments -split ' ' # Simple split, might need better parsing
# Parse arguments string into array, handling quoted strings and standalone "--" separator
$argumentsArray = @()

# Split the arguments string by spaces, but handle quoted strings (both single and double quotes)
$argTokens = [regex]::Matches($Arguments, '(\"[^\"]*\"|''[^'']*''|\S+)') | ForEach-Object { $_.Value.Trim('"', "'") }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The iOS implementation in RunApplication passes the $Arguments array directly to [regex]::Matches without first converting it to a string, unlike the Android path. This will cause incorrect argument parsing.
Severity: CRITICAL | Confidence: High

🔍 Detailed Analysis

In the RunApplication function, the iOS implementation incorrectly handles the $Arguments parameter, which is an array ([string[]]). Unlike the Android implementation which correctly converts the array to a string, the iOS code passes the array directly to [regex]::Matches on line 379. This function expects a string, and relying on PowerShell's implicit array-to-string conversion is fragile and will likely lead to incorrect argument parsing. This will cause iOS apps launched via SauceLabs to receive malformed arguments, potentially leading to launch failures or incorrect application behavior. Additionally, logging on line 363 will be improperly formatted.

💡 Suggested Fix

Before the iOS implementation logic (line 362), convert the $Arguments array to a string using $argumentsString = $this.ConvertArgumentsToString($Arguments). Subsequently, use this new $argumentsString variable for logging (line 363) and in the regex parsing logic (line 379) to ensure arguments are handled correctly.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: app-runner/Private/DeviceProviders/SauceLabsProvider.ps1#L374-L379

Potential issue: In the `RunApplication` function, the iOS implementation incorrectly
handles the `$Arguments` parameter, which is an array (`[string[]]`). Unlike the Android
implementation which correctly converts the array to a string, the iOS code passes the
array directly to `[regex]::Matches` on line 379. This function expects a string, and
relying on PowerShell's implicit array-to-string conversion is fragile and will likely
lead to incorrect argument parsing. This will cause iOS apps launched via SauceLabs to
receive malformed arguments, potentially leading to launch failures or incorrect
application behavior. Additionally, logging on line 363 will be improperly formatted.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 7483056

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants