Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ ignore:
- "pkg/logger/**"
- "pkg/driver/wda/runner.go"
- "pkg/driver/wda/setup.go"
- "pkg/driver/devicelab/webview.go"
- "pkg/driver/devicelab_ios/**"
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/devicelab-dev/maestro-runner

go 1.22.0
go 1.23

require (
github.com/urfave/cli/v2 v2.27.7
Expand All @@ -10,6 +10,7 @@ require (
require (
github.com/Masterminds/semver v1.5.0 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/coder/websocket v1.8.15
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/danielpaulus/go-ios v1.0.131 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
Expand Down Expand Up @@ -53,6 +54,5 @@ require (
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
gvisor.dev/gvisor v0.0.0-20240405191320-0878b34101b5 // indirect
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 // indirect
nhooyr.io/websocket v1.8.17 // indirect
software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/danielpaulus/go-ios v1.0.131 h1:pLe5ZPaZkvF9OIc6AYwU6CVLtxyz3Q+Pz91KVu5LwVM=
Expand Down Expand Up @@ -137,7 +139,5 @@ gvisor.dev/gvisor v0.0.0-20240405191320-0878b34101b5 h1:DOUDfNS+CFMM46k18FRF5k/0
gvisor.dev/gvisor v0.0.0-20240405191320-0878b34101b5/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 h1:AQkaJpH+/FmqRjmXZPELom5zIERYZfwTjnHpfoVMQEc=
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y=
nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE=
software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ=
6 changes: 3 additions & 3 deletions pkg/cloud/saucelabs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func TestReportResult_RDC(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotPath = r.URL.Path
body, _ := io.ReadAll(r.Body)
json.Unmarshal(body, &gotBody)
_ = json.Unmarshal(body, &gotBody)
w.WriteHeader(200)
}))
defer srv.Close()
Expand Down Expand Up @@ -234,7 +234,7 @@ func TestReportResult_VMs(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotPath = r.URL.Path
body, _ := io.ReadAll(r.Body)
json.Unmarshal(body, &gotBody)
_ = json.Unmarshal(body, &gotBody)
w.WriteHeader(200)
}))
defer srv.Close()
Expand Down Expand Up @@ -263,7 +263,7 @@ func TestReportResult_EmptyJobID(t *testing.T) {
func TestUpdateJob_ServerError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
w.Write([]byte("internal error"))
_, _ = w.Write([]byte("internal error"))
}))
defer srv.Close()

Expand Down
6 changes: 3 additions & 3 deletions pkg/device/devicelab_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func (d *AndroidDevice) checkUiAutomationConflict() error {
for _, pkg := range knownConflicts {
if strings.Contains(output, pkg) {
logger.Info("Stopping %s to avoid UiAutomation conflict", pkg)
d.Shell("am force-stop " + pkg)
_, _ = d.Shell("am force-stop " + pkg)
}
}

Expand All @@ -159,7 +159,7 @@ func (d *AndroidDevice) checkUiAutomationConflict() error {
if idx := strings.Index(line, "/"); idx > 0 {
pkg := line[:idx]
logger.Info("Stopping active instrumentation: %s", pkg)
d.Shell("am force-stop " + pkg)
_, _ = d.Shell("am force-stop " + pkg)
}
}
}
Expand Down Expand Up @@ -352,7 +352,7 @@ func checkDeviceLabHandshake(network, address string) bool {
}
defer conn.Close()

conn.SetDeadline(time.Now().Add(2 * time.Second))
_ = conn.SetDeadline(time.Now().Add(2 * time.Second))

// Send a minimal WebSocket upgrade request
handshake := "GET / HTTP/1.1\r\n" +
Expand Down
6 changes: 6 additions & 0 deletions pkg/driver/browser/cdp/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ func New(cfg Config) (*Driver, error) {
Set("disable-background-timer-throttling").
Set("disable-component-update").
Set("password-store", "basic")
// Chrome's setuid sandbox relies on user namespaces, which CI runners
// (e.g. GitHub Actions) restrict — causing the zygote to abort on launch.
// Disable the sandbox only under CI or when explicitly requested.
if os.Getenv("CI") != "" || os.Getenv("MAESTRO_NO_SANDBOX") != "" {
l = l.Set("no-sandbox")
}
if cfg.UserDataDir != "" {
// Persistent profile: cookies / localStorage / extensions survive
// across runs. Caller is responsible for the directory's lifecycle.
Expand Down
10 changes: 5 additions & 5 deletions pkg/driver/browser/cdp/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3712,7 +3712,7 @@ func TestUploadFile(t *testing.T) {

// Create a temp file to upload
tmpFile := t.TempDir() + "/test-upload.txt"
os.WriteFile(tmpFile, []byte("test content"), 0o600)
_ = os.WriteFile(tmpFile, []byte("test content"), 0o600)

step := &flow.UploadFileStep{
BaseStep: flow.BaseStep{StepType: flow.StepUploadFile},
Expand Down Expand Up @@ -3743,8 +3743,8 @@ func TestUploadFileMultiple(t *testing.T) {
defer d.Close()

dir := t.TempDir()
os.WriteFile(dir+"/a.txt", []byte("a"), 0o600)
os.WriteFile(dir+"/b.txt", []byte("b"), 0o600)
_ = os.WriteFile(dir+"/a.txt", []byte("a"), 0o600)
_ = os.WriteFile(dir+"/b.txt", []byte("b"), 0o600)

step := &flow.UploadFileStep{
BaseStep: flow.BaseStep{StepType: flow.StepUploadFile},
Expand Down Expand Up @@ -4586,7 +4586,7 @@ func TestRunBrowserScript(t *testing.T) {
// Create a temp JS file
tmpDir := t.TempDir()
scriptPath := filepath.Join(tmpDir, "test-script.js")
os.WriteFile(scriptPath, []byte(`return document.title;`), 0644)
_ = os.WriteFile(scriptPath, []byte(`return document.title;`), 0644)

result := d.runBrowserScript(&flow.RunBrowserScriptStep{
File: scriptPath,
Expand All @@ -4608,7 +4608,7 @@ func TestRunBrowserScriptWithEnv(t *testing.T) {

tmpDir := t.TempDir()
scriptPath := filepath.Join(tmpDir, "env-script.js")
os.WriteFile(scriptPath, []byte(`return window.__env.API_KEY;`), 0644)
_ = os.WriteFile(scriptPath, []byte(`return window.__env.API_KEY;`), 0644)

result := d.runBrowserScript(&flow.RunBrowserScriptStep{
File: scriptPath,
Expand Down
2 changes: 1 addition & 1 deletion pkg/driver/devicelab/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ func (d *Driver) hideKeyboard(_ *flow.HideKeyboardStep) *core.CommandResult {
// Retry up to 3 times — the on-device agent tries KEYCODE_ESCAPE first
// (keyboard-only, no navigation side-effects), then falls back to KEYCODE_BACK.
for attempt := 0; attempt < 3; attempt++ {
d.client.HideKeyboard()
_ = d.client.HideKeyboard()

// Wait for keyboard to actually disappear (animation ~300ms).
deadline := time.Now().Add(500 * time.Millisecond)
Expand Down
6 changes: 3 additions & 3 deletions pkg/driver/devicelab/commands_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ func TestOpenLink(t *testing.T) {
// browser=true path — force browser via BROWSABLE category
shell.commands = nil
browser := true
res = driver.openLink(&flow.OpenLinkStep{Link: "https://example.com", Browser: &browser})
_ = driver.openLink(&flow.OpenLinkStep{Link: "https://example.com", Browser: &browser})
if !strings.Contains(shell.commands[0], "BROWSABLE") {
t.Errorf("openLink with browser=true should add BROWSABLE category: %s", shell.commands[0])
}
Expand Down Expand Up @@ -681,7 +681,7 @@ func TestStartStopRecording(t *testing.T) {
}

shell.commands = nil
res = driver.startRecording(&flow.StartRecordingStep{Path: "/sdcard/my.mp4"})
_ = driver.startRecording(&flow.StartRecordingStep{Path: "/sdcard/my.mp4"})
if !strings.Contains(shell.commands[0], "/sdcard/my.mp4") {
t.Errorf("expected custom path, got %s", shell.commands[0])
}
Expand Down Expand Up @@ -1791,7 +1791,7 @@ func (s *scriptedClient) SendKeyActions(text string) error {
}

// helper: build a cached Element with text + bounds + action callbacks.
func makeCachedElement(text string, rect uiautomator2.ElementRect, sendKeys func(string) error) *uiautomator2.Element {
func makeCachedElement(text string, rect uiautomator2.ElementRect, sendKeys func(string) error) *uiautomator2.Element { //nolint:unused
elem := uiautomator2.NewCachedElement("elem-id", text, rect)
if sendKeys != nil {
elem.SetSendKeysFunc(sendKeys)
Expand Down
6 changes: 3 additions & 3 deletions pkg/driver/devicelab/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,9 @@ func (d *Driver) tapHadEffectViaWindowUpdate(appID string) bool {
}

const (
tapVerifyAttempts = 3
tapVerifyInterval = 30 * time.Millisecond
windowUpdateTimeoutMs = 500
tapVerifyAttempts = 3 //nolint:unused
tapVerifyInterval = 30 * time.Millisecond //nolint:unused
windowUpdateTimeoutMs = 500 //nolint:unused
)

// recordTap captures the current tree hash so a later failing assertion can
Expand Down
20 changes: 10 additions & 10 deletions pkg/driver/devicelab/webview.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (m *webViewManager) connectViaUnixSocket(cdpInfo *core.CDPInfo, cdpType str
logger.Info("[cdp:5-websocket] connecting CDP WebSocket via unix socket: %s", socketPath)
if err := ws.Connect(connectCtx, "ws://localhost/devtools/browser", nil); err != nil {
logger.Info("[cdp:5-websocket] CDP WebSocket connection failed: %v", err)
m.forwarder.RemoveSocketForward(socketPath)
_ = m.forwarder.RemoveSocketForward(socketPath)
os.Remove(socketPath)
return fmt.Errorf("failed to connect CDP WebSocket: %w", err)
}
Expand All @@ -111,7 +111,7 @@ func (m *webViewManager) connectViaUnixSocket(cdpInfo *core.CDPInfo, cdpType str
browserTimeout := browser.Timeout(10 * time.Second)
if err := browserTimeout.Connect(); err != nil {
logger.Info("[cdp:6-browser] Rod browser connection failed: %v", err)
m.forwarder.RemoveSocketForward(socketPath)
_ = m.forwarder.RemoveSocketForward(socketPath)
os.Remove(socketPath)
return fmt.Errorf("failed to connect Rod browser: %w", err)
}
Expand All @@ -120,7 +120,7 @@ func (m *webViewManager) connectViaUnixSocket(cdpInfo *core.CDPInfo, cdpType str
if err != nil || len(pages) == 0 {
logger.Info("[cdp:6-browser] no pages found in WebView (err=%v)", err)
browser.Close()
m.forwarder.RemoveSocketForward(socketPath)
_ = m.forwarder.RemoveSocketForward(socketPath)
os.Remove(socketPath)
return fmt.Errorf("no pages found in WebView")
}
Expand Down Expand Up @@ -203,7 +203,7 @@ func (m *webViewManager) connectBrowserViaHTTP(cdpInfo *core.CDPInfo, cdpType st
targets, err := m.fetchCDPTargets(socketPath)
if err != nil {
logger.Info("[cdp:5-discover] failed to fetch targets: %v", err)
m.forwarder.RemoveSocketForward(socketPath)
_ = m.forwarder.RemoveSocketForward(socketPath)
os.Remove(socketPath)
return fmt.Errorf("failed to fetch CDP targets: %w", err)
}
Expand All @@ -221,7 +221,7 @@ func (m *webViewManager) connectBrowserViaHTTP(cdpInfo *core.CDPInfo, cdpType st
}
if pageTarget == nil {
logger.Info("[cdp:5-discover] no page targets found in %d targets", len(targets))
m.forwarder.RemoveSocketForward(socketPath)
_ = m.forwarder.RemoveSocketForward(socketPath)
os.Remove(socketPath)
return fmt.Errorf("no page targets found")
}
Expand All @@ -243,7 +243,7 @@ func (m *webViewManager) connectBrowserViaHTTP(cdpInfo *core.CDPInfo, cdpType st
logger.Info("[cdp:6-websocket] connecting CDP WebSocket to page: %s", pageWSPath)
if err := ws.Connect(connectCtx, pageWSPath, nil); err != nil {
logger.Info("[cdp:6-websocket] page WebSocket connection failed: %v", err)
m.forwarder.RemoveSocketForward(socketPath)
_ = m.forwarder.RemoveSocketForward(socketPath)
os.Remove(socketPath)
return fmt.Errorf("failed to connect page WebSocket: %w", err)
}
Expand All @@ -259,7 +259,7 @@ func (m *webViewManager) connectBrowserViaHTTP(cdpInfo *core.CDPInfo, cdpType st

if err := browser.Connect(); err != nil {
logger.Info("[cdp:7-browser] Rod browser connection failed: %v", err)
m.forwarder.RemoveSocketForward(socketPath)
_ = m.forwarder.RemoveSocketForward(socketPath)
os.Remove(socketPath)
return fmt.Errorf("failed to connect Rod browser: %w", err)
}
Expand All @@ -269,7 +269,7 @@ func (m *webViewManager) connectBrowserViaHTTP(cdpInfo *core.CDPInfo, cdpType st
if err != nil {
logger.Info("[cdp:7-browser] PageFromTarget failed: %v", err)
browser.Close()
m.forwarder.RemoveSocketForward(socketPath)
_ = m.forwarder.RemoveSocketForward(socketPath)
os.Remove(socketPath)
return fmt.Errorf("failed to create Rod page: %w", err)
}
Expand Down Expand Up @@ -375,7 +375,7 @@ func (m *webViewManager) disconnectLocked() {
m.network = nil
if m.socketPath != "" {
logger.Info("[cdp:disconnect] removing ADB forward and cleaning up: %s", m.socketPath)
m.forwarder.RemoveSocketForward(m.socketPath)
_ = m.forwarder.RemoveSocketForward(m.socketPath)
os.Remove(m.socketPath)
m.socketPath = ""
}
Expand All @@ -402,7 +402,7 @@ func (m *webViewManager) cleanup() {

if socketPath != "" {
logger.Info("[cdp:cleanup] removing ADB forward: %s", socketPath)
m.forwarder.RemoveSocketForward(socketPath)
_ = m.forwarder.RemoveSocketForward(socketPath)
logger.Info("[cdp:cleanup] removing local socket file: %s", socketPath)
os.Remove(socketPath)
m.socketPath = ""
Expand Down
2 changes: 1 addition & 1 deletion pkg/driver/devicelab_ios/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -1159,7 +1159,7 @@ func flattenArguments(args map[string]any) []string {
return out
}

func bytesEqual(a, b []byte) bool {
func bytesEqual(a, b []byte) bool { //nolint:unused
if len(a) != len(b) {
return false
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/driver/devicelab_ios/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func (d *Driver) parentContext() context.Context {
return d.ctx
}

func (d *Driver) currentBundleID() string { return d.appID }
func (d *Driver) currentBundleID() string { return d.appID } //nolint:unused

// callTimeout returns a context with the find timeout applied (or a sane
// default of 10s if unset).
Expand Down
2 changes: 1 addition & 1 deletion pkg/driver/devicelab_ios/pagesource.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// Selector semantics mirror the existing wda driver: substring/regex on
// text+label+value+placeholder, exact-or-regex on id (accessibility
// identifier), state filters, width/height tolerance.
func findInSnapshot(nodes []SnapshotNode, sel flow.Selector) []*SnapshotNode {
func findInSnapshot(nodes []SnapshotNode, sel flow.Selector) []*SnapshotNode { //nolint:unused
if len(nodes) == 0 {
return nil
}
Expand Down
10 changes: 5 additions & 5 deletions pkg/driver/devicelab_ios/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,13 @@ func Setup(ctx context.Context, opts SetupOptions) (*Client, *RunnerHandle, erro
attempt-1, maxStartupAttempts, lastErr,
)
fmt.Fprintln(os.Stderr, banner)
fmt.Fprintln(os.Stderr, fmt.Sprintf(" ↻ Retrying (attempt %d/%d)...", attempt, maxStartupAttempts))
fmt.Fprintf(os.Stderr, " ↻ Retrying (attempt %d/%d)...\n", attempt, maxStartupAttempts)
// Mirror into the runner log so the artifact captures the full
// retry history (logFile may be closed if we hit the fallback
// branch above; guard before writing).
if opts.Stdout != os.Stderr {
fmt.Fprintln(opts.Stdout, banner)
fmt.Fprintln(opts.Stdout, fmt.Sprintf("=== attempt %d/%d ===", attempt, maxStartupAttempts))
fmt.Fprintf(opts.Stdout, "=== attempt %d/%d ===\n", attempt, maxStartupAttempts)
}
// Reset the simulator before retrying. Killing xcodebuild
// alone doesn't unwedge a stuck CoreSimulator daemon — if
Expand All @@ -185,14 +185,14 @@ func Setup(ctx context.Context, opts SetupOptions) (*Client, *RunnerHandle, erro
// Best-effort: log and continue. If reset fails the
// retry attempt will reveal whether the sim is still
// usable.
fmt.Fprintln(os.Stderr, fmt.Sprintf(" ⚠ simctl reset failed: %v (continuing anyway)", rerr))
fmt.Fprintf(os.Stderr, " ⚠ simctl reset failed: %v (continuing anyway)\n", rerr)
}
}

client, handle, err := startOnce(ctx, opts, xctestrun, logPath)
if err == nil {
if attempt > 1 {
fmt.Fprintln(os.Stderr, fmt.Sprintf(" ✓ Runner started on attempt %d/%d", attempt, maxStartupAttempts))
fmt.Fprintf(os.Stderr, " ✓ Runner started on attempt %d/%d\n", attempt, maxStartupAttempts)
}
return client, handle, nil
}
Expand Down Expand Up @@ -312,7 +312,7 @@ func injectPortIntoXctestrun(path string, port int) error {
// usable again.
func resetSimulator(ctx context.Context, udid string, logOut io.Writer) error {
if logOut != nil {
fmt.Fprintln(logOut, fmt.Sprintf(" ⟳ Resetting simulator %s...", udid))
fmt.Fprintf(logOut, " ⟳ Resetting simulator %s...\n", udid)
}
shutdownCmd := exec.CommandContext(ctx, "xcrun", "simctl", "shutdown", udid)
if out, err := shutdownCmd.CombinedOutput(); err != nil {
Expand Down
Loading
Loading