Logs
Overview
GPT Driver creates XCTest Attachments which can be directly viewed in XCode after a test run
GPT Driver also creates a Session URL which contains a log of each step executed via GPT Driver. This URL is hosted on the GPT Driver web platform.
How to retrieve the GPT Driver Session URL?
The GPTDriver session URL is made available through multiple channels:
OS Logs - Structured logging with
.noticelevel (maps to OSLogType.default)XCTest Attachments - Saved as
.weblocfile in test resultsPublic Property - Accessible programmatically via
driver.sessionURLCallback -
onSessionCreatedclosure for real-time access
Method 1: Extract from XCTest Attachments
The session URL is automatically saved as an XCTAttachment in your test results.
Location in Test Results
The attachment is saved with:
Name:
GPTDriver Session URL LinkLifetime:
.keepAlways(persists even after test completion)UserInfo: Contains
["link": "<session-url>"]File Format:
.webloc(macOS web location file in XML format)
Extracting from .xcresult Bundle
.xcresult BundleUsing Python Script
#!/usr/bin/env python3
"""
Extract GPTDriver session URL from .xcresult bundle
"""
import json
import subprocess
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
def extract_session_url_from_xcresult(xcresult_path: str) -> str | None:
"""Extract session URL from .xcresult bundle using xcresulttool."""
try:
# Get JSON representation of attachments
result = subprocess.run(
["xcrun", "xcresulttool", "get", "--path", xcresult_path, "--format", "json"],
capture_output=True,
text=True,
check=True
)
data = json.loads(result.stdout)
# Recursively search for the attachment
def find_attachment(obj, target_name="GPTDriver Session URL Link"):
if isinstance(obj, dict):
if obj.get("name") == target_name:
# Check userInfo for link
if "userInfo" in obj and "link" in obj["userInfo"]:
return obj["userInfo"]["link"]
# Or check if there's a payload reference
if "payloadRef" in obj:
return extract_from_payload(obj["payloadRef"]["id"], xcresult_path)
# Recursively search
for value in obj.values():
result = find_attachment(value, target_name)
if result:
return result
elif isinstance(obj, list):
for item in obj:
result = find_attachment(item, target_name)
if result:
return result
return None
def extract_from_payload(id_ref: str, xcresult_path: str) -> str | None:
"""Extract URL from webloc file attachment."""
try:
# Export the attachment
temp_dir = Path("/tmp/xcresult_extract")
temp_dir.mkdir(exist_ok=True)
subprocess.run(
["xcrun", "xcresulttool", "export", "--path", xcresult_path,
"--id", id_ref, "--type", "file", "--output-path", str(temp_dir / "attachment.webloc")],
check=True,
capture_output=True
)
# Parse the webloc XML file
webloc_path = temp_dir / "attachment.webloc"
if webloc_path.exists():
tree = ET.parse(webloc_path)
root = tree.getroot()
# webloc format: <dict><key>URL</key><string>https://...</string></dict>
url_elem = root.find(".//string")
if url_elem is not None:
return url_elem.text
except Exception as e:
print(f"Error extracting payload: {e}", file=sys.stderr)
return None
return find_attachment(data)
except Exception as e:
print(f"Error processing xcresult: {e}", file=sys.stderr)
return None
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: extract_session_url.py <path-to-xcresult>")
sys.exit(1)
url = extract_session_url_from_xcresult(sys.argv[1])
if url:
print(url)
else:
print("Session URL not found", file=sys.stderr)
sys.exit(1)Using Swift Script
#!/usr/bin/env swift
import Foundation
// Extract session URL from xcresult bundle
func extractSessionURL(from xcresultPath: String) -> String? {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/xcrun")
process.arguments = ["xcresulttool", "get", "--path", xcresultPath, "--format", "json"]
let pipe = Pipe()
process.standardOutput = pipe
do {
try process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
return nil
}
// Recursively search for attachment with name "GPTDriver Session URL Link"
func findAttachment(in obj: Any) -> String? {
if let dict = obj as? [String: Any] {
if dict["name"] as? String == "GPTDriver Session URL Link",
let userInfo = dict["userInfo"] as? [String: Any],
let link = userInfo["link"] as? String {
return link
}
for value in dict.values {
if let result = findAttachment(in: value) {
return result
}
}
} else if let array = obj as? [Any] {
for item in array {
if let result = findAttachment(in: item) {
return result
}
}
}
return nil
}
return findAttachment(in: json)
} catch {
print("Error: \(error)", to: &FileHandle.standardError)
return nil
}
}
// Usage
if CommandLine.arguments.count < 2 {
print("Usage: extract_session_url.swift <path-to-xcresult>", to: &FileHandle.standardError)
exit(1)
}
if let url = extractSessionURL(from: CommandLine.arguments[1]) {
print(url)
} else {
print("Session URL not found", to: &FileHandle.standardError)
exit(1)
}Method 2: Extract from OS Logs
The session URL is logged with .notice level using the unified logging system.
Log Format
The log entry follows this format:
[gptdriver][session:<session-id>] Live Session View | sessionURL=https://app.mobileboost.io/gpt-driver/sessions/<session-id>Extracting from Log Files
Using log command (macOS)
# Show recent GPTDriver logs
log show --predicate 'subsystem == "com.gptdriver"' --last 1h
# Extract session URLs from logs
log show --predicate 'subsystem == "com.gptdriver" AND eventMessage CONTAINS "Live Session View"' \
--last 1h | grep -o 'sessionURL=[^|]*' | cut -d= -f2
# Save to file
log show --predicate 'subsystem == "com.gptdriver"' --last 1h > gptdriver_logs.txt
grep "sessionURL" gptdriver_logs.txt | sed 's/.*sessionURL=\([^|]*\).*/\1/'Using os_log Export (CI)
If you're exporting logs in CI, you can parse them:
# In your CI script, export logs before test completion
xcrun simctl spawn booted log collect --output gptdriver_logs.logarchive 2>/dev/null || true
# Extract session URLs
log show --archive gptdriver_logs.logarchive \
--predicate 'subsystem == "com.gptdriver" AND eventMessage CONTAINS "Live Session View"' \
| grep -o 'sessionURL=[^|]*' | cut -d= -f2Method 3: Programmatic Access
Using the Public Property
let driver = GptDriver(apiKey: "your-api-key")
try await driver.execute("Your command")
// Access session URL directly
if let sessionURL = driver.sessionURL {
print("Session URL: \(sessionURL)")
// Upload to CI artifact storage, send to Slack, etc.
}Using the Callback
let driver = GptDriver(apiKey: "your-api-key")
// Set up callback to capture session URL immediately
driver.onSessionCreated = { sessionURL in
print("Session created: \(sessionURL)")
// Example: Save to file for CI
let url = URL(fileURLWithPath: "session_url.txt")
try? sessionURL.write(to: url, atomically: true, encoding: .utf8)
// Example: Set as environment variable (if running in CI)
setenv("GPTDRIVER_SESSION_URL", sessionURL, 1)
// Example: Send to CI notification service
// sendToSlack(sessionURL)
// postToGitHubActions(sessionURL)
}
try await driver.execute("Your command")CI Integration Examples
GitHub Actions
name: XCTest CI
on: [push]
jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Run Tests
run: |
xcodebuild test \
-scheme YourScheme \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-resultBundlePath TestResults.xcresult
- name: Extract Session URL
id: extract_url
run: |
SESSION_URL=$(xcrun xcresulttool get --path TestResults.xcresult --format json | \
python3 -c "import sys, json; \
data = json.load(sys.stdin); \
def find_url(obj): \
if isinstance(obj, dict): \
if obj.get('name') == 'GPTDriver Session URL Link' and 'userInfo' in obj: \
return obj['userInfo'].get('link'); \
for v in obj.values(): \
r = find_url(v); \
if r: return r; \
elif isinstance(obj, list): \
for item in obj: \
r = find_url(item); \
if r: return r; \
return None; \
print(find_url(data) or '')")
echo "url=$SESSION_URL" >> $GITHUB_OUTPUT
- name: Comment Session URL
if: steps.extract_url.outputs.url != ''
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `GPTDriver Session: ${{ steps.extract_url.outputs.url }}`
})
- name: Upload Test Results
uses: actions/upload-artifact@v3
with:
name: test-results
path: TestResults.xcresultTroubleshooting
Session URL Not Found
Check if session was created: Ensure
startSession()completed successfullyVerify attachment lifetime: The attachment uses
.keepAlways- check your test result bundle settingsCheck log level: Ensure
.noticelevel logs are captured in your CI environment
Parsing Issues
JSON format: The
xcresulttoolJSON output structure may vary - use recursive searchWebloc format: The
.weblocfile is XML format - use proper XML parsingEncoding: Ensure UTF-8 encoding when reading files
Performance
For large test suites, extracting attachments can be slow
Consider using the callback method (
onSessionCreated) for real-time accessCache session URLs if running multiple tests
Best Practices
Use the callback: Set
onSessionCreatedfor immediate accessSave to file: Write session URL to a file early in your test run
Include in test reports: Attach session URL to your test reporting tool
Set environment variable: Make session URL available to other CI steps
Archive results: Keep
.xcresultbundles for debugging
Last updated