package main import ( "context" "fmt" "os" "time" "github.com/chromedp/chromedp" ) func main() { if len(os.Args) < 3 { fmt.Println("Usage: test-browser-login ") os.Exit(1) } email := os.Args[1] password := os.Args[2] // Set up Chrome options - VISIBLE browser for debugging opts := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.Flag("headless", false), // NOT headless - we want to see it chromedp.Flag("disable-gpu", false), ) // Create allocator context allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) defer cancel() // Create context with timeout ctx, cancel := chromedp.NewContext(allocCtx) defer cancel() // Set a reasonable timeout for the entire login process ctx, cancel = context.WithTimeout(ctx, 120*time.Second) defer cancel() var token string // Run the browser automation tasks err := chromedp.Run(ctx, // Navigate to Kosmi chromedp.Navigate("https://app.kosmi.io"), // Wait for page to load completely chromedp.WaitReady("body"), chromedp.Sleep(2*time.Second), // Click Login button chromedp.ActionFunc(func(ctx context.Context) error { fmt.Println("Looking for Log in button...") var buttonTexts []string chromedp.Evaluate(` Array.from(document.querySelectorAll('button')).map(el => el.textContent.trim()) `, &buttonTexts).Do(ctx) fmt.Printf("Found buttons: %v\n", buttonTexts) var found bool if err := chromedp.Evaluate(` (() => { const buttons = Array.from(document.querySelectorAll('button')); // Try both "Login" and "Log in" const btn = buttons.find(el => { const text = el.textContent.trim(); return text === 'Login' || text === 'Log in'; }); if (btn) { btn.click(); return true; } return false; })() `, &found).Do(ctx); err != nil { return err } if !found { return fmt.Errorf("Login button not found") } fmt.Println("✓ Clicked Login button") return nil }), // Wait for login modal chromedp.Sleep(3*time.Second), // Click "Login with Email" button chromedp.ActionFunc(func(ctx context.Context) error { fmt.Println("Looking for Login with Email button...") var buttonTexts []string chromedp.Evaluate(` Array.from(document.querySelectorAll('button')).map(el => el.textContent.trim()) `, &buttonTexts).Do(ctx) fmt.Printf("Found buttons: %v\n", buttonTexts) var found bool if err := chromedp.Evaluate(` (() => { const btn = Array.from(document.querySelectorAll('button')).find(el => el.textContent.includes('Email')); if (btn) { btn.click(); return true; } return false; })() `, &found).Do(ctx); err != nil { return err } if !found { return fmt.Errorf("Login with Email button not found") } fmt.Println("✓ Clicked Login with Email button") return nil }), // Wait for email form chromedp.Sleep(3*time.Second), chromedp.WaitVisible(`input[type="password"]`, chromedp.ByQuery), chromedp.ActionFunc(func(ctx context.Context) error { fmt.Println("Password input found, filling in email...") var inputTypes []string chromedp.Evaluate(` Array.from(document.querySelectorAll('input')).map(el => el.type + (el.placeholder ? ' ('+el.placeholder+')' : '')) `, &inputTypes).Do(ctx) fmt.Printf("Available inputs: %v\n", inputTypes) return nil }), // Click on the email input to focus it chromedp.Click(`input[placeholder*="Email"], input[placeholder*="Username"]`, chromedp.ByQuery), chromedp.Sleep(200*time.Millisecond), // Type email character by character chromedp.ActionFunc(func(ctx context.Context) error { fmt.Printf("Typing email: %s\n", email) return chromedp.SendKeys(`input[placeholder*="Email"], input[placeholder*="Username"]`, email, chromedp.ByQuery).Do(ctx) }), chromedp.Sleep(500*time.Millisecond), // Click on the password input to focus it chromedp.Click(`input[type="password"]`, chromedp.ByQuery), chromedp.Sleep(200*time.Millisecond), // Type password character by character chromedp.ActionFunc(func(ctx context.Context) error { fmt.Printf("Typing password (length: %d)...\n", len(password)) return chromedp.SendKeys(`input[type="password"]`, password, chromedp.ByQuery).Do(ctx) }), // Verify password was filled correctly chromedp.ActionFunc(func(ctx context.Context) error { var actualLength int chromedp.Evaluate(` (() => { const passwordInput = document.querySelector('input[type="password"]'); return passwordInput ? passwordInput.value.length : 0; })() `, &actualLength).Do(ctx) fmt.Printf("✓ Password filled (actual length in browser: %d, expected: %d)\n", actualLength, len(password)) if actualLength != len(password) { return fmt.Errorf("password length mismatch: got %d, expected %d", actualLength, len(password)) } return nil }), chromedp.Sleep(500*time.Millisecond), chromedp.ActionFunc(func(ctx context.Context) error { fmt.Println("Looking for submit button...") var buttonTexts []string chromedp.Evaluate(` Array.from(document.querySelectorAll('button')).map(el => el.textContent.trim() + (el.disabled ? ' (disabled)' : '')) `, &buttonTexts).Do(ctx) fmt.Printf("Submit buttons available: %v\n", buttonTexts) return nil }), // Wait a moment for form validation chromedp.Sleep(1*time.Second), // Click the login submit button (be very specific) chromedp.ActionFunc(func(ctx context.Context) error { fmt.Println("Attempting to click submit button...") var result string if err := chromedp.Evaluate(` (() => { const buttons = Array.from(document.querySelectorAll('button')); console.log('All buttons:', buttons.map(b => ({ text: b.textContent.trim(), disabled: b.disabled, visible: b.offsetParent !== null }))); // Find the submit button in the login form // It should be visible, enabled, and contain "Login" but not be the main nav button const submitBtn = buttons.find(el => { const text = el.textContent.trim(); const isLoginBtn = text === 'Login' || text.startsWith('Login'); const isEnabled = !el.disabled; const isVisible = el.offsetParent !== null; const isInForm = el.closest('form') !== null || el.closest('[role="dialog"]') !== null; return isLoginBtn && isEnabled && isVisible && isInForm; }); if (submitBtn) { console.log('Found submit button:', submitBtn.textContent.trim()); submitBtn.click(); return 'CLICKED: ' + submitBtn.textContent.trim(); } return 'NOT_FOUND'; })() `, &result).Do(ctx); err != nil { return err } fmt.Printf("Submit button result: %s\n", result) if result == "NOT_FOUND" { return fmt.Errorf("Login submit button not found or not clickable") } fmt.Println("✓ Clicked submit button") return nil }), // Wait for login to complete chromedp.Sleep(5*time.Second), // Check for errors chromedp.ActionFunc(func(ctx context.Context) error { var errorText string chromedp.Evaluate(` (() => { const errorEl = document.querySelector('[role="alert"], .error, .alert-error'); return errorEl ? errorEl.textContent : ''; })() `, &errorText).Do(ctx) if errorText != "" { fmt.Printf("❌ Login error: %s\n", errorText) return fmt.Errorf("login failed: %s", errorText) } fmt.Println("✓ No error messages") return nil }), // Extract token from localStorage chromedp.Evaluate(`localStorage.getItem('token')`, &token), // Check token details chromedp.ActionFunc(func(ctx context.Context) error { var userInfo string chromedp.Evaluate(` (() => { try { const token = localStorage.getItem('token'); if (!token) return 'NO_TOKEN'; const parts = token.split('.'); if (parts.length !== 3) return 'INVALID_TOKEN'; const payload = JSON.parse(atob(parts[1])); return JSON.stringify(payload, null, 2); } catch (e) { return 'ERROR: ' + e.message; } })() `, &userInfo).Do(ctx) fmt.Printf("\n=== Token Payload ===\n%s\n", userInfo) return nil }), // Keep browser open for inspection chromedp.ActionFunc(func(ctx context.Context) error { fmt.Println("\n✓ Login complete! Browser will stay open for 30 seconds for inspection...") return nil }), chromedp.Sleep(30*time.Second), ) if err != nil { fmt.Printf("\n❌ Error: %v\n", err) os.Exit(1) } if token == "" { fmt.Println("\n❌ No token found in localStorage") os.Exit(1) } fmt.Printf("\n✅ Token obtained (length: %d)\n", len(token)) fmt.Printf("First 50 chars: %s...\n", token[:min(50, len(token))]) } func min(a, b int) int { if a < b { return a } return b }