From f2761fb0dfb8aec00798cc8261551d4fb3f96675 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 11 Dec 2025 17:53:44 +0000 Subject: [PATCH 1/2] feat: add fix && tests --- images/chromium-headless/image/wrapper.sh | 1 - server/e2e/e2e_chromium_test.go | 74 +++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/images/chromium-headless/image/wrapper.sh b/images/chromium-headless/image/wrapper.sh index 36bddbb4..004187a3 100755 --- a/images/chromium-headless/image/wrapper.sh +++ b/images/chromium-headless/image/wrapper.sh @@ -70,7 +70,6 @@ if [ -z "${CHROMIUM_FLAGS:-}" ]; then --no-first-run \ --no-sandbox \ --no-service-autorun \ - --no-startup-window \ --ozone-platform=headless \ --password-store=basic \ --unsafely-disable-devtools-self-xss-warnings \ diff --git a/server/e2e/e2e_chromium_test.go b/server/e2e/e2e_chromium_test.go index 4c91f925..219abd19 100644 --- a/server/e2e/e2e_chromium_test.go +++ b/server/e2e/e2e_chromium_test.go @@ -810,3 +810,77 @@ func TestPlaywrightExecuteAPI(t *testing.T) { logger.Info("[test]", "result", "playwright execute API test passed") } + +// TestCDPTargetCreation tests that headless browsers can create new targets via CDP. +func TestCDPTargetCreation(t *testing.T) { + image := headlessImage + name := containerName + "-cdp-target" + + logger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelInfo})) + baseCtx := logctx.AddToContext(context.Background(), logger) + + if _, err := exec.LookPath("docker"); err != nil { + require.NoError(t, err, "docker not available: %v", err) + } + + // Clean slate + _ = stopContainer(baseCtx, name) + + // Start container + width, height := 1024, 768 + _, exitCh, err := runContainer(baseCtx, image, name, map[string]string{"WIDTH": strconv.Itoa(width), "HEIGHT": strconv.Itoa(height)}) + require.NoError(t, err, "failed to start container: %v", err) + defer stopContainer(baseCtx, name) + + ctx, cancel := context.WithTimeout(baseCtx, 2*time.Minute) + defer cancel() + + logger.Info("[test]", "action", "waiting for API") + require.NoError(t, waitHTTPOrExit(ctx, apiBaseURL+"/spec.yaml", exitCh), "api not ready") + + // Wait for CDP endpoint to be ready (via the devtools proxy) + logger.Info("[test]", "action", "waiting for CDP endpoint") + require.NoError(t, waitTCP(ctx, "127.0.0.1:9222"), "CDP endpoint not ready") + + // Wait for Chromium to be fully initialized by checking if CDP responds + logger.Info("[test]", "action", "waiting for Chromium to be fully ready") + var initialTargets []map[string]interface{} + targets, err := listCDPTargets(ctx) + if err == nil { + initialTargets = targets + } + + // Use CDP HTTP API to list targets (avoids Playwright's implicit page creation) + logger.Info("[test]", "action", "listing initial targets via CDP HTTP API") + initialPageCount := 0 + for _, target := range initialTargets { + if targetType, ok := target["type"].(string); ok && targetType == "page" { + initialPageCount++ + } + } + logger.Info("[test]", "initial_page_count", initialPageCount, "total_targets", len(initialTargets)) + + // Headless browser should start with at least 1 page target. + // If --no-startup-window is enabled, the browser will start with 0 pages, + // which will cause Target.createTarget to fail with "no browser is open (-32000)". + require.GreaterOrEqual(t, initialPageCount, 1, + "headless browser should start with at least 1 page target (got %d). "+ + "This usually means --no-startup-window flag is enabled in wrapper.sh, "+ + "which causes browsers to start without any pages.", initialPageCount) +} + +// listCDPTargets lists all CDP targets via the HTTP API (inside the container) +func listCDPTargets(ctx context.Context) ([]map[string]interface{}, error) { + // Use the internal CDP HTTP endpoint (port 9223) inside the container + stdout, err := execCombinedOutput(ctx, "curl", []string{"-s", "http://localhost:9223/json/list"}) + if err != nil { + return nil, fmt.Errorf("curl failed: %w, output: %s", err, stdout) + } + + var targets []map[string]interface{} + if err := json.Unmarshal([]byte(stdout), &targets); err != nil { + return nil, fmt.Errorf("failed to parse targets JSON: %w, output: %s", err, stdout) + } + + return targets, nil +} From d5c455660fd9c7abdab8526f4c86ae15a999cc82 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Fri, 12 Dec 2025 15:23:06 +0000 Subject: [PATCH 2/2] review: error handling --- server/e2e/e2e_chromium_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/e2e/e2e_chromium_test.go b/server/e2e/e2e_chromium_test.go index 219abd19..b0a6a2e4 100644 --- a/server/e2e/e2e_chromium_test.go +++ b/server/e2e/e2e_chromium_test.go @@ -844,21 +844,21 @@ func TestCDPTargetCreation(t *testing.T) { // Wait for Chromium to be fully initialized by checking if CDP responds logger.Info("[test]", "action", "waiting for Chromium to be fully ready") - var initialTargets []map[string]interface{} targets, err := listCDPTargets(ctx) - if err == nil { - initialTargets = targets + if err != nil { + logger.Error("[test]", "error", err.Error()) + require.Fail(t, "failed to list CDP targets") } // Use CDP HTTP API to list targets (avoids Playwright's implicit page creation) logger.Info("[test]", "action", "listing initial targets via CDP HTTP API") initialPageCount := 0 - for _, target := range initialTargets { + for _, target := range targets { if targetType, ok := target["type"].(string); ok && targetType == "page" { initialPageCount++ } } - logger.Info("[test]", "initial_page_count", initialPageCount, "total_targets", len(initialTargets)) + logger.Info("[test]", "initial_page_count", initialPageCount, "total_targets", len(targets)) // Headless browser should start with at least 1 page target. // If --no-startup-window is enabled, the browser will start with 0 pages,