diff --git a/codecov.yml b/codecov.yml index 7f2b81a..30b920b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -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/**" diff --git a/go.mod b/go.mod index e7eceac..066a17c 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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 @@ -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 ) diff --git a/go.sum b/go.sum index 6d9aa44..f9212db 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= diff --git a/pkg/cloud/saucelabs_test.go b/pkg/cloud/saucelabs_test.go index 32abaa4..683b79a 100644 --- a/pkg/cloud/saucelabs_test.go +++ b/pkg/cloud/saucelabs_test.go @@ -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() @@ -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() @@ -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() diff --git a/pkg/device/devicelab_driver.go b/pkg/device/devicelab_driver.go index 06791e5..c6c883f 100644 --- a/pkg/device/devicelab_driver.go +++ b/pkg/device/devicelab_driver.go @@ -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) } } @@ -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) } } } @@ -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" + diff --git a/pkg/driver/browser/cdp/driver.go b/pkg/driver/browser/cdp/driver.go index 209479d..310e61f 100644 --- a/pkg/driver/browser/cdp/driver.go +++ b/pkg/driver/browser/cdp/driver.go @@ -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. diff --git a/pkg/driver/browser/cdp/driver_test.go b/pkg/driver/browser/cdp/driver_test.go index 7941af0..25b698c 100644 --- a/pkg/driver/browser/cdp/driver_test.go +++ b/pkg/driver/browser/cdp/driver_test.go @@ -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}, @@ -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}, @@ -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, @@ -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, diff --git a/pkg/driver/devicelab/commands.go b/pkg/driver/devicelab/commands.go index b0bb789..796b1e3 100644 --- a/pkg/driver/devicelab/commands.go +++ b/pkg/driver/devicelab/commands.go @@ -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) diff --git a/pkg/driver/devicelab/commands_helpers_test.go b/pkg/driver/devicelab/commands_helpers_test.go index c1aef7d..17d38e1 100644 --- a/pkg/driver/devicelab/commands_helpers_test.go +++ b/pkg/driver/devicelab/commands_helpers_test.go @@ -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]) } @@ -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]) } @@ -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) diff --git a/pkg/driver/devicelab/driver.go b/pkg/driver/devicelab/driver.go index 15f8e93..feb5efd 100644 --- a/pkg/driver/devicelab/driver.go +++ b/pkg/driver/devicelab/driver.go @@ -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 diff --git a/pkg/driver/devicelab/webview.go b/pkg/driver/devicelab/webview.go index 4053c96..11443e8 100644 --- a/pkg/driver/devicelab/webview.go +++ b/pkg/driver/devicelab/webview.go @@ -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) } @@ -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) } @@ -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") } @@ -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) } @@ -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") } @@ -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) } @@ -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) } @@ -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) } @@ -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 = "" } @@ -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 = "" diff --git a/pkg/driver/devicelab_ios/commands.go b/pkg/driver/devicelab_ios/commands.go index e74c6bc..1de2bb2 100644 --- a/pkg/driver/devicelab_ios/commands.go +++ b/pkg/driver/devicelab_ios/commands.go @@ -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 } diff --git a/pkg/driver/devicelab_ios/driver.go b/pkg/driver/devicelab_ios/driver.go index cabf8f8..c62ee29 100644 --- a/pkg/driver/devicelab_ios/driver.go +++ b/pkg/driver/devicelab_ios/driver.go @@ -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). diff --git a/pkg/driver/devicelab_ios/pagesource.go b/pkg/driver/devicelab_ios/pagesource.go index 55e78d7..b73f8a6 100644 --- a/pkg/driver/devicelab_ios/pagesource.go +++ b/pkg/driver/devicelab_ios/pagesource.go @@ -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 } diff --git a/pkg/driver/devicelab_ios/setup.go b/pkg/driver/devicelab_ios/setup.go index 830e636..e4962ac 100644 --- a/pkg/driver/devicelab_ios/setup.go +++ b/pkg/driver/devicelab_ios/setup.go @@ -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 @@ -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 } @@ -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 { diff --git a/pkg/driver/wda/commands_test.go b/pkg/driver/wda/commands_test.go index 4501876..cfb822b 100644 --- a/pkg/driver/wda/commands_test.go +++ b/pkg/driver/wda/commands_test.go @@ -1693,14 +1693,14 @@ func TestSetWaitForIdleTimeoutEnable(t *testing.T) { if r.Method == "POST" && strings.Contains(r.URL.Path, "/appium/settings") { body, _ := io.ReadAll(r.Body) var req map[string]interface{} - json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req) if s, ok := req["settings"].(map[string]interface{}); ok { settingsReceived = s } - json.NewEncoder(w).Encode(map[string]interface{}{"value": nil, "sessionId": "s1"}) + _ = json.NewEncoder(w).Encode(map[string]interface{}{"value": nil, "sessionId": "s1"}) return } - json.NewEncoder(w).Encode(map[string]interface{}{"value": nil, "sessionId": "s1"}) + _ = json.NewEncoder(w).Encode(map[string]interface{}{"value": nil, "sessionId": "s1"}) })) defer server.Close() @@ -1729,14 +1729,14 @@ func TestSetWaitForIdleTimeoutDisable(t *testing.T) { if r.Method == "POST" && strings.Contains(r.URL.Path, "/appium/settings") { body, _ := io.ReadAll(r.Body) var req map[string]interface{} - json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req) if s, ok := req["settings"].(map[string]interface{}); ok { settingsReceived = s } - json.NewEncoder(w).Encode(map[string]interface{}{"value": nil, "sessionId": "s1"}) + _ = json.NewEncoder(w).Encode(map[string]interface{}{"value": nil, "sessionId": "s1"}) return } - json.NewEncoder(w).Encode(map[string]interface{}{"value": nil, "sessionId": "s1"}) + _ = json.NewEncoder(w).Encode(map[string]interface{}{"value": nil, "sessionId": "s1"}) })) defer server.Close() @@ -1762,7 +1762,7 @@ func TestSetWaitForIdleTimeoutDefault(t *testing.T) { if r.Method == "POST" && strings.Contains(r.URL.Path, "/appium/settings") { called = true } - json.NewEncoder(w).Encode(map[string]interface{}{"value": nil, "sessionId": "s1"}) + _ = json.NewEncoder(w).Encode(map[string]interface{}{"value": nil, "sessionId": "s1"}) })) defer server.Close() diff --git a/pkg/driver/wda/driver.go b/pkg/driver/wda/driver.go index ce60338..0415446 100644 --- a/pkg/driver/wda/driver.go +++ b/pkg/driver/wda/driver.go @@ -689,6 +689,7 @@ func (d *Driver) findElementQuick(sel flow.Selector, timeoutMs int) (*core.Eleme // wrapper views that XCUITest can't classify as accessible but which clearly // contain visible content. Hidden-but-still-mounted screens (all descendants // invisible) are correctly excluded. +//nolint:unused func filterVisibleOrHostingVisible(candidates []*ParsedElement) []*ParsedElement { out := candidates[:0] for _, c := range candidates { diff --git a/pkg/driver/wda/runner.go b/pkg/driver/wda/runner.go index bb52fb0..074f4c7 100644 --- a/pkg/driver/wda/runner.go +++ b/pkg/driver/wda/runner.go @@ -202,13 +202,13 @@ func (r *Runner) Start(ctx context.Context) error { 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 banner into the runner log so the failure artifact // captures the full retry history. Use append mode so we // don't lose the previous attempt's xcodebuild output. if f, ferr := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o644); ferr == nil { fmt.Fprintln(f, banner) - fmt.Fprintln(f, fmt.Sprintf("=== attempt %d/%d ===", attempt, maxStartupAttempts)) + fmt.Fprintf(f, "=== attempt %d/%d ===\n", attempt, maxStartupAttempts) _ = f.Close() } // Reset the simulator before retrying. Killing xcodebuild alone @@ -220,7 +220,7 @@ func (r *Runner) Start(ctx context.Context) error { // wedge pattern anyway). if r.isSimulatorCache { if rerr := resetSimulator(ctx, r.deviceUDID); rerr != nil { - 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) } } } @@ -228,7 +228,7 @@ func (r *Runner) Start(ctx context.Context) error { err := r.startOnce(ctx, xctestrun, logPath, attempt) if err == nil { if attempt > 1 { - fmt.Fprintln(os.Stderr, fmt.Sprintf(" ✓ WDA started on attempt %d/%d", attempt, maxStartupAttempts)) + fmt.Fprintf(os.Stderr, " ✓ WDA started on attempt %d/%d\n", attempt, maxStartupAttempts) } // For physical devices, forward the WDA port from device to localhost. if !r.isSimulatorCache { diff --git a/pkg/emulator/exec_test.go b/pkg/emulator/exec_test.go index 2a78883..733dc85 100644 --- a/pkg/emulator/exec_test.go +++ b/pkg/emulator/exec_test.go @@ -21,7 +21,7 @@ func fakeExecError() func(name string, args ...string) *exec.Cmd { // fakeExecPerCmd dispatches stdout by command-args prefix match. // First match wins. Use the FIRST argument (the binary name like "adb") plus // the first sub-arg if you need to disambiguate. Unmatched calls run "false". -func fakeExecPerCmd(cases map[string]string) func(name string, args ...string) *exec.Cmd { +func fakeExecPerCmd(cases map[string]string) func(name string, args ...string) *exec.Cmd { //nolint:unused return func(name string, args ...string) *exec.Cmd { key := name if len(args) > 0 { diff --git a/pkg/executor/tap_options.go b/pkg/executor/tap_options.go index 34e6f99..9962cbe 100644 --- a/pkg/executor/tap_options.go +++ b/pkg/executor/tap_options.go @@ -57,6 +57,7 @@ const ( // settleAfterAction waits for the UI to settle after a UI-mutating action. // Matches Maestro's behavior of calling waitForAppToSettle() after every action. // Uses DeviceLab's native event-based settle when available, falls back to hierarchy comparison. +//nolint:unused func (fr *FlowRunner) settleAfterAction() { // Check if driver supports native settle (DeviceLab) if settler, ok := core.Unwrap(fr.driver).(interface { @@ -76,6 +77,7 @@ func (fr *FlowRunner) settleAfterAction() { } // isTapAction returns true if the step is a tap that may trigger a screen transition. +//nolint:unused func isTapAction(step flow.Step) bool { switch step.(type) { case *flow.TapOnStep, *flow.DoubleTapOnStep, *flow.LongPressOnStep, *flow.TapOnPointStep: @@ -88,6 +90,7 @@ func isTapAction(step flow.Step) bool { // needsPreSettle returns true if the step needs the UI to be settled before executing. // These steps don't call findElement (which has implicit idle wait), so they need // explicit settle to avoid timing issues after screen transitions. +//nolint:unused func needsPreSettle(step flow.Step) bool { switch step.(type) { case *flow.InputTextStep, *flow.InputRandomStep, *flow.EraseTextStep: diff --git a/pkg/flutter/vmservice.go b/pkg/flutter/vmservice.go index 37d1799..66e8c8f 100644 --- a/pkg/flutter/vmservice.go +++ b/pkg/flutter/vmservice.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "nhooyr.io/websocket" + "github.com/coder/websocket" ) const ( diff --git a/pkg/flutter/vmservice_test.go b/pkg/flutter/vmservice_test.go index 4c013ee..450f01d 100644 --- a/pkg/flutter/vmservice_test.go +++ b/pkg/flutter/vmservice_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "nhooyr.io/websocket" + "github.com/coder/websocket" ) // mockVMServiceHandler handles JSON-RPC requests for testing. @@ -69,7 +69,7 @@ func (h *mockVMServiceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) "error": map[string]interface{}{"code": -32601, "message": "method not found"}, } respData, _ := json.Marshal(resp) - conn.Write(ctx, websocket.MessageText, respData) + _ = conn.Write(ctx, websocket.MessageText, respData) continue } @@ -79,7 +79,7 @@ func (h *mockVMServiceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) "result": result, } respData, _ := json.Marshal(resp) - conn.Write(ctx, websocket.MessageText, respData) + _ = conn.Write(ctx, websocket.MessageText, respData) } } @@ -211,7 +211,7 @@ func TestConnectNoFlutterIsolate(t *testing.T) { "result": result, } respData, _ := json.Marshal(resp) - conn.Write(ctx, websocket.MessageText, respData) + _ = conn.Write(ctx, websocket.MessageText, respData) } }) server := httptest.NewServer(handler) @@ -241,7 +241,7 @@ func TestCallRPCError(t *testing.T) { } var req jsonRPCRequest - json.Unmarshal(data, &req) + _ = json.Unmarshal(data, &req) resp := map[string]interface{}{ "jsonrpc": "2.0", @@ -249,7 +249,7 @@ func TestCallRPCError(t *testing.T) { "error": map[string]interface{}{"code": -32000, "message": "test error"}, } respData, _ := json.Marshal(resp) - conn.Write(ctx, websocket.MessageText, respData) + _ = conn.Write(ctx, websocket.MessageText, respData) } }) server := httptest.NewServer(handler) diff --git a/pkg/maestro/adapter_test.go b/pkg/maestro/adapter_test.go index 0397242..d0f54fa 100644 --- a/pkg/maestro/adapter_test.go +++ b/pkg/maestro/adapter_test.go @@ -104,7 +104,7 @@ func TestAdapterClick(t *testing.T) { } params, _ := json.Marshal(req.Params) var p map[string]interface{} - json.Unmarshal(params, &p) + _ = json.Unmarshal(params, &p) if p["x"].(float64) != 100 || p["y"].(float64) != 200 { t.Errorf("expected x=100,y=200, got %v,%v", p["x"], p["y"]) } diff --git a/pkg/maestro/client.go b/pkg/maestro/client.go index 7af17ce..3d237b0 100644 --- a/pkg/maestro/client.go +++ b/pkg/maestro/client.go @@ -14,7 +14,7 @@ import ( "sync/atomic" "time" - "nhooyr.io/websocket" + "github.com/coder/websocket" ) const ( diff --git a/pkg/maestro/client_test.go b/pkg/maestro/client_test.go index 64b0e99..9a77147 100644 --- a/pkg/maestro/client_test.go +++ b/pkg/maestro/client_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "nhooyr.io/websocket" + "github.com/coder/websocket" ) // mockServer creates an httptest.Server that upgrades to WebSocket @@ -258,7 +258,7 @@ func TestClientCallWithParams(t *testing.T) { // Verify params were passed params, _ := json.Marshal(req.Params) var p map[string]interface{} - json.Unmarshal(params, &p) + _ = json.Unmarshal(params, &p) x, _ := p["x"].(float64) y, _ := p["y"].(float64)