Skip to main content

Browser

Struct Browser 

Source
pub struct Browser { /* private fields */ }

Implementations§

Source§

impl Browser

Source

pub fn builder() -> BrowserBuilder

Examples found in repository?
examples/youtube_test.rs (line 7)
4async fn main() {
5    println!("=== YouTube Test ===\n");
6
7    let browser = Browser::builder()
8        .headful()
9        .profile(7, 777)
10        .build()
11        .await
12        .expect("launch failed");
13
14    let page = browser.new_page("https://www.youtube.com").await.expect("nav failed");
15    page.wait(3000).await;
16
17    let title = page.js("document.title").await.unwrap_or_default();
18    let url = page.url().await.unwrap_or_default();
19    println!("Title: {title}");
20    println!("URL: {url}");
21
22    println!("\nBrowser open. Ctrl+C to exit.");
23    loop {
24        tokio::time::sleep(std::time::Duration::from_secs(60)).await;
25    }
26}
More examples
Hide additional examples
examples/smoke_test.rs (line 7)
4async fn main() {
5    println!("=== Smoke Test ===\n");
6
7    let browser = Browser::builder()
8        .headful()
9        .profile(7, 777)
10        .build()
11        .await
12        .expect("launch failed");
13
14    let page = browser.new_page("about:blank").await.expect("page failed");
15
16    let checks = &[
17        ("webdriver", "navigator.webdriver.toString()"),
18        ("timezone", "Intl.DateTimeFormat().resolvedOptions().timeZone"),
19        ("cores", "navigator.hardwareConcurrency.toString()"),
20        ("screen", "screen.width+'x'+screen.height"),
21        ("UA", "navigator.userAgent"),
22    ];
23    for (name, expr) in checks {
24        let val = page.js(expr).await.unwrap_or_default();
25        println!("{name}: {val}");
26    }
27
28    println!("\nBrowser open. Ctrl+C to exit.");
29    loop {
30        tokio::time::sleep(std::time::Duration::from_secs(60)).await;
31    }
32}
examples/game_test.rs (line 52)
30async fn main() {
31    load_dotenv();
32    println!("=== Game Test + WS Detection ===\n");
33
34    // 1. Fetch game URL
35    println!("Fetching game URL...");
36    let client = reqwest::Client::new();
37    let resp = client
38        .post(api_url())
39        .header("Authorization", format!("Bearer {}", api_token()))
40        .header("Content-Type", "application/json")
41        .header("Accept", "application/json")
42        .body(r#"{"user_id": "beezsbee"}"#)
43        .send()
44        .await
45        .expect("API request failed");
46
47    let body: serde_json::Value = resp.json().await.expect("JSON parse failed");
48    let game_url = body["url"].as_str().expect("no 'url' in response");
49    println!("Game URL: {game_url}\n");
50
51    // 2. Launch browser
52    let browser = Browser::builder()
53        .headful()
54        .profile(42, 12345)
55        .build()
56        .await
57        .expect("browser launch failed");
58
59    // 3. Open blank page first so we can set up listeners before navigation
60    let page = browser.new_page("about:blank").await.expect("page failed");
61
62    // Enable network domain for WS events
63    page.cdp().execute(EnableParams::default()).await.expect("network enable failed");
64
65    // Set up WS event listeners
66    let mut ws_created = page.cdp().event_listener::<EventWebSocketCreated>().await.expect("listener failed");
67    let mut ws_closed = page.cdp().event_listener::<EventWebSocketClosed>().await.expect("listener failed");
68
69    // Spawn listener tasks
70    tokio::spawn(async move {
71        while let Some(ev) = ws_created.next().await {
72            println!("[WS CREATED] request_id={:?} url={}", ev.request_id, ev.url);
73        }
74    });
75
76    tokio::spawn(async move {
77        while let Some(ev) = ws_closed.next().await {
78            println!("[WS CLOSED]  request_id={:?}", ev.request_id);
79        }
80    });
81
82    // 4. Navigate to game (human simulation runs automatically)
83    println!("Navigating to game...");
84    page.navigate(game_url).await.expect("nav failed");
85
86    // 5. Wait and monitor
87    println!("Watching for WebSocket connections...\n");
88    loop {
89        tokio::time::sleep(std::time::Duration::from_secs(60)).await;
90    }
91}
examples/tls_check.rs (line 25)
21async fn main() {
22    load_dotenv();
23    println!("=== TLS/HTTP2 Fingerprint Check ===\n");
24
25    let browser = Browser::builder()
26        .headful()
27        .random()
28        .build()
29        .await
30        .expect("browser launch failed");
31
32    let page = browser.new_page("about:blank").await.expect("page failed");
33
34    // 1. Check TLS fingerprint
35    println!("--- tls.peet.ws ---");
36    page.goto("https://tls.peet.ws/api/all").await.expect("nav failed");
37    tokio::time::sleep(std::time::Duration::from_secs(3)).await;
38
39    let body = page.js("document.body.innerText").await.unwrap_or_default();
40    if let Ok(v) = serde_json::from_str::<serde_json::Value>(&body) {
41        // TLS
42        if let Some(tls) = v.get("tls") {
43            println!("JA3 hash:    {}", tls.get("ja3_hash").and_then(|v| v.as_str()).unwrap_or("N/A"));
44            println!("JA3:         {}", tls.get("ja3").and_then(|v| v.as_str()).unwrap_or("N/A"));
45            println!("JA4:         {}", tls.get("ja4").and_then(|v| v.as_str()).unwrap_or("N/A"));
46            println!("TLS version: {}", tls.get("version").and_then(|v| v.as_str()).unwrap_or("N/A"));
47        }
48
49        // HTTP2
50        println!();
51        if let Some(h2) = v.get("http2") {
52            println!("HTTP2 fingerprint:   {}", h2.get("akamai_fingerprint").and_then(|v| v.as_str()).unwrap_or("N/A"));
53            println!("Pseudo-header order: {}", h2.get("sent_pseudo_header_fields_order").unwrap_or(&serde_json::Value::Null));
54            if let Some(settings) = h2.get("sent_settings") {
55                println!("SETTINGS:            {}", settings);
56            }
57        }
58
59        // HTTP version used
60        println!();
61        if let Some(ip) = v.get("ip") {
62            println!("Client IP:   {}", ip.as_str().unwrap_or("N/A"));
63        }
64        if let Some(http_version) = v.get("http_version") {
65            println!("HTTP version: {}", http_version.as_str().unwrap_or("N/A"));
66        }
67
68        // User-Agent
69        println!();
70        if let Some(h1) = v.get("http1") {
71            if let Some(headers) = h1.get("headers") {
72                if let Some(arr) = headers.as_array() {
73                    for h in arr {
74                        let name = h.get("name").and_then(|n| n.as_str()).unwrap_or("");
75                        let val = h.get("value").and_then(|n| n.as_str()).unwrap_or("");
76                        if name.to_lowercase() == "user-agent" {
77                            println!("User-Agent:  {}", val);
78                        }
79                    }
80                }
81            }
82        }
83    } else {
84        println!("Failed to parse response: {}", &body[..body.len().min(500)]);
85    }
86
87    // 2. Check known Chrome 134 reference values
88    println!("\n--- Expected Chrome 134 stock values ---");
89    println!("JA3 hash:    cd49876058a26ccf43e241ccc4e4b5d3  (Chrome 134 Windows)");
90    println!("JA4:         t13d1517h2_8daaf6152771_02713d6af8  (Chrome 134 Windows)");
91    println!("HTTP2 Akamai: 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p");
92
93    println!("\nBrowser still open. Press Ctrl+C to exit.");
94    loop {
95        tokio::time::sleep(std::time::Duration::from_secs(60)).await;
96    }
97}
examples/batch_test.rs (line 175)
110async fn main() {
111    load_dotenv();
112    let total = PER_ROUND * ROUNDS;
113    println!("=== Proxy batch: {}×{} = {} | live {}s | rotate {}s cooldown ===\n",
114        PER_ROUND, ROUNDS, total, BROWSER_LIVE_SECS, ROTATE_COOLDOWN_SECS);
115
116    let start = Instant::now();
117    let mut ok = 0u32;
118    let mut fail = 0u32;
119
120    for round in 0..ROUNDS {
121        println!("--- Round {}/{} ---", round + 1, ROUNDS);
122
123        // 1. Rotate IP and get proxy
124        let proxy = match rotate_and_get_proxy().await {
125            Some(p) => p,
126            None => {
127                println!("  Skip round — no proxy");
128                fail += PER_ROUND as u32;
129                continue;
130            }
131        };
132
133        // 2. Verify proxy works
134        if !verify_proxy(&proxy).await {
135            println!("  Skip round — proxy dead");
136            fail += PER_ROUND as u32;
137            continue;
138        }
139
140        // 3. Launch 3 browsers in parallel
141        let offset = round * PER_ROUND;
142        let mut handles = Vec::new();
143
144        for i in 0..PER_ROUND {
145            let idx = offset + i;
146            let user_id = USERS[idx % USERS.len()].to_string();
147            let proxy = proxy.clone();
148            let api = api_url();
149            let token = api_token();
150
151            handles.push(tokio::spawn(async move {
152                let label = format!("[{:02}/{}]", idx + 1, total);
153                let t = Instant::now();
154
155                // Get game URL (direct, no proxy)
156                let game_url = match reqwest::Client::new()
157                    .post(&api)
158                    .header("Authorization", format!("Bearer {}", token))
159                    .header("Content-Type", "application/json")
160                    .body(format!(r#"{{"user_id": "{}"}}"#, user_id))
161                    .send().await
162                {
163                    Ok(resp) => {
164                        let body: serde_json::Value = resp.json().await.unwrap_or_default();
165                        match body["url"].as_str() {
166                            Some(u) if !u.is_empty() => u.to_string(),
167                            _ => return format!("{label} FAIL api: {body}"),
168                        }
169                    }
170                    Err(e) => return format!("{label} FAIL api: {e}"),
171                };
172                let api_ms = t.elapsed().as_millis();
173
174                // Launch browser with proxy
175                let browser = match Browser::builder()
176                    .headful()
177                    .random()
178                    .proxy(&proxy)
179                    .build().await
180                {
181                    Ok(b) => b,
182                    Err(e) => return format!("{label} FAIL launch: {e}"),
183                };
184                let launch_ms = t.elapsed().as_millis() - api_ms;
185
186                // Navigate (human simulation auto-starts with aggressive scroll)
187                let page = match browser.new_page("about:blank").await {
188                    Ok(p) => p,
189                    Err(e) => return format!("{label} FAIL page: {e}"),
190                };
191
192                // Aggressive scrolling in background
193                let scroll_page = page.cdp().clone();
194                let scroll_handle = tokio::spawn(async move {
195                    use chromiumoxide::cdp::browser_protocol::input::{
196                        DispatchMouseEventParams, DispatchMouseEventType,
197                    };
198                    let mut y = 300.0f64;
199                    loop {
200                        // Scroll down aggressively
201                        let mut ev = DispatchMouseEventParams::new(
202                            DispatchMouseEventType::MouseWheel, 500.0, y,
203                        );
204                        ev.delta_x = Some(0.0);
205                        ev.delta_y = Some(300.0);
206                        let _ = scroll_page.execute(ev).await;
207                        y = ((y + 50.0) % 800.0) + 100.0;
208                        tokio::time::sleep(std::time::Duration::from_millis(800)).await;
209                    }
210                });
211
212                let t_nav = Instant::now();
213                if let Err(e) = page.goto(&game_url).await {
214                    scroll_handle.abort();
215                    return format!("{label} FAIL nav: {e}");
216                }
217                let nav_ms = t_nav.elapsed().as_millis();
218
219                // Wait for cookies
220                tokio::time::sleep(std::time::Duration::from_secs(5)).await;
221
222                let cookies = page.cdp().get_cookies().await.unwrap_or_default();
223                let has_evo = cookies.iter().any(|c| c.name == "EVOSESSIONID");
224                let evo = cookies.iter().find(|c| c.name == "EVOSESSIONID")
225                    .map(|c| c.value.clone())
226                    .unwrap_or_else(|| "N/A".into());
227                let cookie_names: Vec<String> = cookies.iter().map(|c| c.name.clone()).collect();
228
229                // Stay alive for remaining time (human sim + aggressive scroll running)
230                let elapsed = t.elapsed().as_secs();
231                if elapsed < BROWSER_LIVE_SECS {
232                    tokio::time::sleep(std::time::Duration::from_secs(BROWSER_LIVE_SECS - elapsed)).await;
233                }
234
235                // Close browser
236                scroll_handle.abort();
237                let _ = browser.close().await;
238                let total_ms = t.elapsed().as_millis();
239
240                let status = if has_evo { "OK" } else { "MISS" };
241                format!("{label} {status} | {user_id:<18} | total:{total_ms}ms api:{api_ms}ms launch:{launch_ms}ms nav:{nav_ms}ms | cookies:{} | EVO:{evo}\n       {:?}",
242                    cookies.len(), cookie_names)
243            }));
244        }
245
246        for h in handles {
247            match h.await {
248                Ok(msg) => {
249                    if msg.contains("] OK ") { ok += 1; } else { fail += 1; }
250                    println!("{msg}");
251                }
252                Err(e) => { println!("  task panic: {e}"); fail += 1; }
253            }
254        }
255
256        println!();
257    }
258
259    let elapsed = start.elapsed().as_secs_f64();
260    println!("=== OK:{ok} FAIL:{fail} | {elapsed:.0}s total | {:.1}s/profile ===",
261        elapsed / total as f64);
262}
Source

pub async fn connect(ws_url: &str) -> Result<Self>

Connect to an already-running Chrome at ws_url.

Source

pub async fn new_page(&self, url: &str) -> Result<Page>

Open a new tab and navigate to URL. Background human simulation (mouse movement + scrolling) starts automatically.

Examples found in repository?
examples/youtube_test.rs (line 14)
4async fn main() {
5    println!("=== YouTube Test ===\n");
6
7    let browser = Browser::builder()
8        .headful()
9        .profile(7, 777)
10        .build()
11        .await
12        .expect("launch failed");
13
14    let page = browser.new_page("https://www.youtube.com").await.expect("nav failed");
15    page.wait(3000).await;
16
17    let title = page.js("document.title").await.unwrap_or_default();
18    let url = page.url().await.unwrap_or_default();
19    println!("Title: {title}");
20    println!("URL: {url}");
21
22    println!("\nBrowser open. Ctrl+C to exit.");
23    loop {
24        tokio::time::sleep(std::time::Duration::from_secs(60)).await;
25    }
26}
More examples
Hide additional examples
examples/smoke_test.rs (line 14)
4async fn main() {
5    println!("=== Smoke Test ===\n");
6
7    let browser = Browser::builder()
8        .headful()
9        .profile(7, 777)
10        .build()
11        .await
12        .expect("launch failed");
13
14    let page = browser.new_page("about:blank").await.expect("page failed");
15
16    let checks = &[
17        ("webdriver", "navigator.webdriver.toString()"),
18        ("timezone", "Intl.DateTimeFormat().resolvedOptions().timeZone"),
19        ("cores", "navigator.hardwareConcurrency.toString()"),
20        ("screen", "screen.width+'x'+screen.height"),
21        ("UA", "navigator.userAgent"),
22    ];
23    for (name, expr) in checks {
24        let val = page.js(expr).await.unwrap_or_default();
25        println!("{name}: {val}");
26    }
27
28    println!("\nBrowser open. Ctrl+C to exit.");
29    loop {
30        tokio::time::sleep(std::time::Duration::from_secs(60)).await;
31    }
32}
examples/game_test.rs (line 60)
30async fn main() {
31    load_dotenv();
32    println!("=== Game Test + WS Detection ===\n");
33
34    // 1. Fetch game URL
35    println!("Fetching game URL...");
36    let client = reqwest::Client::new();
37    let resp = client
38        .post(api_url())
39        .header("Authorization", format!("Bearer {}", api_token()))
40        .header("Content-Type", "application/json")
41        .header("Accept", "application/json")
42        .body(r#"{"user_id": "beezsbee"}"#)
43        .send()
44        .await
45        .expect("API request failed");
46
47    let body: serde_json::Value = resp.json().await.expect("JSON parse failed");
48    let game_url = body["url"].as_str().expect("no 'url' in response");
49    println!("Game URL: {game_url}\n");
50
51    // 2. Launch browser
52    let browser = Browser::builder()
53        .headful()
54        .profile(42, 12345)
55        .build()
56        .await
57        .expect("browser launch failed");
58
59    // 3. Open blank page first so we can set up listeners before navigation
60    let page = browser.new_page("about:blank").await.expect("page failed");
61
62    // Enable network domain for WS events
63    page.cdp().execute(EnableParams::default()).await.expect("network enable failed");
64
65    // Set up WS event listeners
66    let mut ws_created = page.cdp().event_listener::<EventWebSocketCreated>().await.expect("listener failed");
67    let mut ws_closed = page.cdp().event_listener::<EventWebSocketClosed>().await.expect("listener failed");
68
69    // Spawn listener tasks
70    tokio::spawn(async move {
71        while let Some(ev) = ws_created.next().await {
72            println!("[WS CREATED] request_id={:?} url={}", ev.request_id, ev.url);
73        }
74    });
75
76    tokio::spawn(async move {
77        while let Some(ev) = ws_closed.next().await {
78            println!("[WS CLOSED]  request_id={:?}", ev.request_id);
79        }
80    });
81
82    // 4. Navigate to game (human simulation runs automatically)
83    println!("Navigating to game...");
84    page.navigate(game_url).await.expect("nav failed");
85
86    // 5. Wait and monitor
87    println!("Watching for WebSocket connections...\n");
88    loop {
89        tokio::time::sleep(std::time::Duration::from_secs(60)).await;
90    }
91}
examples/tls_check.rs (line 32)
21async fn main() {
22    load_dotenv();
23    println!("=== TLS/HTTP2 Fingerprint Check ===\n");
24
25    let browser = Browser::builder()
26        .headful()
27        .random()
28        .build()
29        .await
30        .expect("browser launch failed");
31
32    let page = browser.new_page("about:blank").await.expect("page failed");
33
34    // 1. Check TLS fingerprint
35    println!("--- tls.peet.ws ---");
36    page.goto("https://tls.peet.ws/api/all").await.expect("nav failed");
37    tokio::time::sleep(std::time::Duration::from_secs(3)).await;
38
39    let body = page.js("document.body.innerText").await.unwrap_or_default();
40    if let Ok(v) = serde_json::from_str::<serde_json::Value>(&body) {
41        // TLS
42        if let Some(tls) = v.get("tls") {
43            println!("JA3 hash:    {}", tls.get("ja3_hash").and_then(|v| v.as_str()).unwrap_or("N/A"));
44            println!("JA3:         {}", tls.get("ja3").and_then(|v| v.as_str()).unwrap_or("N/A"));
45            println!("JA4:         {}", tls.get("ja4").and_then(|v| v.as_str()).unwrap_or("N/A"));
46            println!("TLS version: {}", tls.get("version").and_then(|v| v.as_str()).unwrap_or("N/A"));
47        }
48
49        // HTTP2
50        println!();
51        if let Some(h2) = v.get("http2") {
52            println!("HTTP2 fingerprint:   {}", h2.get("akamai_fingerprint").and_then(|v| v.as_str()).unwrap_or("N/A"));
53            println!("Pseudo-header order: {}", h2.get("sent_pseudo_header_fields_order").unwrap_or(&serde_json::Value::Null));
54            if let Some(settings) = h2.get("sent_settings") {
55                println!("SETTINGS:            {}", settings);
56            }
57        }
58
59        // HTTP version used
60        println!();
61        if let Some(ip) = v.get("ip") {
62            println!("Client IP:   {}", ip.as_str().unwrap_or("N/A"));
63        }
64        if let Some(http_version) = v.get("http_version") {
65            println!("HTTP version: {}", http_version.as_str().unwrap_or("N/A"));
66        }
67
68        // User-Agent
69        println!();
70        if let Some(h1) = v.get("http1") {
71            if let Some(headers) = h1.get("headers") {
72                if let Some(arr) = headers.as_array() {
73                    for h in arr {
74                        let name = h.get("name").and_then(|n| n.as_str()).unwrap_or("");
75                        let val = h.get("value").and_then(|n| n.as_str()).unwrap_or("");
76                        if name.to_lowercase() == "user-agent" {
77                            println!("User-Agent:  {}", val);
78                        }
79                    }
80                }
81            }
82        }
83    } else {
84        println!("Failed to parse response: {}", &body[..body.len().min(500)]);
85    }
86
87    // 2. Check known Chrome 134 reference values
88    println!("\n--- Expected Chrome 134 stock values ---");
89    println!("JA3 hash:    cd49876058a26ccf43e241ccc4e4b5d3  (Chrome 134 Windows)");
90    println!("JA4:         t13d1517h2_8daaf6152771_02713d6af8  (Chrome 134 Windows)");
91    println!("HTTP2 Akamai: 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p");
92
93    println!("\nBrowser still open. Press Ctrl+C to exit.");
94    loop {
95        tokio::time::sleep(std::time::Duration::from_secs(60)).await;
96    }
97}
examples/batch_test.rs (line 187)
110async fn main() {
111    load_dotenv();
112    let total = PER_ROUND * ROUNDS;
113    println!("=== Proxy batch: {}×{} = {} | live {}s | rotate {}s cooldown ===\n",
114        PER_ROUND, ROUNDS, total, BROWSER_LIVE_SECS, ROTATE_COOLDOWN_SECS);
115
116    let start = Instant::now();
117    let mut ok = 0u32;
118    let mut fail = 0u32;
119
120    for round in 0..ROUNDS {
121        println!("--- Round {}/{} ---", round + 1, ROUNDS);
122
123        // 1. Rotate IP and get proxy
124        let proxy = match rotate_and_get_proxy().await {
125            Some(p) => p,
126            None => {
127                println!("  Skip round — no proxy");
128                fail += PER_ROUND as u32;
129                continue;
130            }
131        };
132
133        // 2. Verify proxy works
134        if !verify_proxy(&proxy).await {
135            println!("  Skip round — proxy dead");
136            fail += PER_ROUND as u32;
137            continue;
138        }
139
140        // 3. Launch 3 browsers in parallel
141        let offset = round * PER_ROUND;
142        let mut handles = Vec::new();
143
144        for i in 0..PER_ROUND {
145            let idx = offset + i;
146            let user_id = USERS[idx % USERS.len()].to_string();
147            let proxy = proxy.clone();
148            let api = api_url();
149            let token = api_token();
150
151            handles.push(tokio::spawn(async move {
152                let label = format!("[{:02}/{}]", idx + 1, total);
153                let t = Instant::now();
154
155                // Get game URL (direct, no proxy)
156                let game_url = match reqwest::Client::new()
157                    .post(&api)
158                    .header("Authorization", format!("Bearer {}", token))
159                    .header("Content-Type", "application/json")
160                    .body(format!(r#"{{"user_id": "{}"}}"#, user_id))
161                    .send().await
162                {
163                    Ok(resp) => {
164                        let body: serde_json::Value = resp.json().await.unwrap_or_default();
165                        match body["url"].as_str() {
166                            Some(u) if !u.is_empty() => u.to_string(),
167                            _ => return format!("{label} FAIL api: {body}"),
168                        }
169                    }
170                    Err(e) => return format!("{label} FAIL api: {e}"),
171                };
172                let api_ms = t.elapsed().as_millis();
173
174                // Launch browser with proxy
175                let browser = match Browser::builder()
176                    .headful()
177                    .random()
178                    .proxy(&proxy)
179                    .build().await
180                {
181                    Ok(b) => b,
182                    Err(e) => return format!("{label} FAIL launch: {e}"),
183                };
184                let launch_ms = t.elapsed().as_millis() - api_ms;
185
186                // Navigate (human simulation auto-starts with aggressive scroll)
187                let page = match browser.new_page("about:blank").await {
188                    Ok(p) => p,
189                    Err(e) => return format!("{label} FAIL page: {e}"),
190                };
191
192                // Aggressive scrolling in background
193                let scroll_page = page.cdp().clone();
194                let scroll_handle = tokio::spawn(async move {
195                    use chromiumoxide::cdp::browser_protocol::input::{
196                        DispatchMouseEventParams, DispatchMouseEventType,
197                    };
198                    let mut y = 300.0f64;
199                    loop {
200                        // Scroll down aggressively
201                        let mut ev = DispatchMouseEventParams::new(
202                            DispatchMouseEventType::MouseWheel, 500.0, y,
203                        );
204                        ev.delta_x = Some(0.0);
205                        ev.delta_y = Some(300.0);
206                        let _ = scroll_page.execute(ev).await;
207                        y = ((y + 50.0) % 800.0) + 100.0;
208                        tokio::time::sleep(std::time::Duration::from_millis(800)).await;
209                    }
210                });
211
212                let t_nav = Instant::now();
213                if let Err(e) = page.goto(&game_url).await {
214                    scroll_handle.abort();
215                    return format!("{label} FAIL nav: {e}");
216                }
217                let nav_ms = t_nav.elapsed().as_millis();
218
219                // Wait for cookies
220                tokio::time::sleep(std::time::Duration::from_secs(5)).await;
221
222                let cookies = page.cdp().get_cookies().await.unwrap_or_default();
223                let has_evo = cookies.iter().any(|c| c.name == "EVOSESSIONID");
224                let evo = cookies.iter().find(|c| c.name == "EVOSESSIONID")
225                    .map(|c| c.value.clone())
226                    .unwrap_or_else(|| "N/A".into());
227                let cookie_names: Vec<String> = cookies.iter().map(|c| c.name.clone()).collect();
228
229                // Stay alive for remaining time (human sim + aggressive scroll running)
230                let elapsed = t.elapsed().as_secs();
231                if elapsed < BROWSER_LIVE_SECS {
232                    tokio::time::sleep(std::time::Duration::from_secs(BROWSER_LIVE_SECS - elapsed)).await;
233                }
234
235                // Close browser
236                scroll_handle.abort();
237                let _ = browser.close().await;
238                let total_ms = t.elapsed().as_millis();
239
240                let status = if has_evo { "OK" } else { "MISS" };
241                format!("{label} {status} | {user_id:<18} | total:{total_ms}ms api:{api_ms}ms launch:{launch_ms}ms nav:{nav_ms}ms | cookies:{} | EVO:{evo}\n       {:?}",
242                    cookies.len(), cookie_names)
243            }));
244        }
245
246        for h in handles {
247            match h.await {
248                Ok(msg) => {
249                    if msg.contains("] OK ") { ok += 1; } else { fail += 1; }
250                    println!("{msg}");
251                }
252                Err(e) => { println!("  task panic: {e}"); fail += 1; }
253            }
254        }
255
256        println!();
257    }
258
259    let elapsed = start.elapsed().as_secs_f64();
260    println!("=== OK:{ok} FAIL:{fail} | {elapsed:.0}s total | {:.1}s/profile ===",
261        elapsed / total as f64);
262}
Source

pub async fn pages(&self) -> Result<Vec<Page>>

Get all open pages (each gets its own background human simulation).

Source

pub async fn cookies(&self) -> Result<Vec<Cookie>>

Get all browser cookies.

Source

pub fn cdp(&self) -> &CdpBrowser

Access underlying chromiumoxide Browser for raw CDP.

Source

pub async fn close(self) -> Result<()>

Gracefully shut down the browser.

Examples found in repository?
examples/batch_test.rs (line 237)
110async fn main() {
111    load_dotenv();
112    let total = PER_ROUND * ROUNDS;
113    println!("=== Proxy batch: {}×{} = {} | live {}s | rotate {}s cooldown ===\n",
114        PER_ROUND, ROUNDS, total, BROWSER_LIVE_SECS, ROTATE_COOLDOWN_SECS);
115
116    let start = Instant::now();
117    let mut ok = 0u32;
118    let mut fail = 0u32;
119
120    for round in 0..ROUNDS {
121        println!("--- Round {}/{} ---", round + 1, ROUNDS);
122
123        // 1. Rotate IP and get proxy
124        let proxy = match rotate_and_get_proxy().await {
125            Some(p) => p,
126            None => {
127                println!("  Skip round — no proxy");
128                fail += PER_ROUND as u32;
129                continue;
130            }
131        };
132
133        // 2. Verify proxy works
134        if !verify_proxy(&proxy).await {
135            println!("  Skip round — proxy dead");
136            fail += PER_ROUND as u32;
137            continue;
138        }
139
140        // 3. Launch 3 browsers in parallel
141        let offset = round * PER_ROUND;
142        let mut handles = Vec::new();
143
144        for i in 0..PER_ROUND {
145            let idx = offset + i;
146            let user_id = USERS[idx % USERS.len()].to_string();
147            let proxy = proxy.clone();
148            let api = api_url();
149            let token = api_token();
150
151            handles.push(tokio::spawn(async move {
152                let label = format!("[{:02}/{}]", idx + 1, total);
153                let t = Instant::now();
154
155                // Get game URL (direct, no proxy)
156                let game_url = match reqwest::Client::new()
157                    .post(&api)
158                    .header("Authorization", format!("Bearer {}", token))
159                    .header("Content-Type", "application/json")
160                    .body(format!(r#"{{"user_id": "{}"}}"#, user_id))
161                    .send().await
162                {
163                    Ok(resp) => {
164                        let body: serde_json::Value = resp.json().await.unwrap_or_default();
165                        match body["url"].as_str() {
166                            Some(u) if !u.is_empty() => u.to_string(),
167                            _ => return format!("{label} FAIL api: {body}"),
168                        }
169                    }
170                    Err(e) => return format!("{label} FAIL api: {e}"),
171                };
172                let api_ms = t.elapsed().as_millis();
173
174                // Launch browser with proxy
175                let browser = match Browser::builder()
176                    .headful()
177                    .random()
178                    .proxy(&proxy)
179                    .build().await
180                {
181                    Ok(b) => b,
182                    Err(e) => return format!("{label} FAIL launch: {e}"),
183                };
184                let launch_ms = t.elapsed().as_millis() - api_ms;
185
186                // Navigate (human simulation auto-starts with aggressive scroll)
187                let page = match browser.new_page("about:blank").await {
188                    Ok(p) => p,
189                    Err(e) => return format!("{label} FAIL page: {e}"),
190                };
191
192                // Aggressive scrolling in background
193                let scroll_page = page.cdp().clone();
194                let scroll_handle = tokio::spawn(async move {
195                    use chromiumoxide::cdp::browser_protocol::input::{
196                        DispatchMouseEventParams, DispatchMouseEventType,
197                    };
198                    let mut y = 300.0f64;
199                    loop {
200                        // Scroll down aggressively
201                        let mut ev = DispatchMouseEventParams::new(
202                            DispatchMouseEventType::MouseWheel, 500.0, y,
203                        );
204                        ev.delta_x = Some(0.0);
205                        ev.delta_y = Some(300.0);
206                        let _ = scroll_page.execute(ev).await;
207                        y = ((y + 50.0) % 800.0) + 100.0;
208                        tokio::time::sleep(std::time::Duration::from_millis(800)).await;
209                    }
210                });
211
212                let t_nav = Instant::now();
213                if let Err(e) = page.goto(&game_url).await {
214                    scroll_handle.abort();
215                    return format!("{label} FAIL nav: {e}");
216                }
217                let nav_ms = t_nav.elapsed().as_millis();
218
219                // Wait for cookies
220                tokio::time::sleep(std::time::Duration::from_secs(5)).await;
221
222                let cookies = page.cdp().get_cookies().await.unwrap_or_default();
223                let has_evo = cookies.iter().any(|c| c.name == "EVOSESSIONID");
224                let evo = cookies.iter().find(|c| c.name == "EVOSESSIONID")
225                    .map(|c| c.value.clone())
226                    .unwrap_or_else(|| "N/A".into());
227                let cookie_names: Vec<String> = cookies.iter().map(|c| c.name.clone()).collect();
228
229                // Stay alive for remaining time (human sim + aggressive scroll running)
230                let elapsed = t.elapsed().as_secs();
231                if elapsed < BROWSER_LIVE_SECS {
232                    tokio::time::sleep(std::time::Duration::from_secs(BROWSER_LIVE_SECS - elapsed)).await;
233                }
234
235                // Close browser
236                scroll_handle.abort();
237                let _ = browser.close().await;
238                let total_ms = t.elapsed().as_millis();
239
240                let status = if has_evo { "OK" } else { "MISS" };
241                format!("{label} {status} | {user_id:<18} | total:{total_ms}ms api:{api_ms}ms launch:{launch_ms}ms nav:{nav_ms}ms | cookies:{} | EVO:{evo}\n       {:?}",
242                    cookies.len(), cookie_names)
243            }));
244        }
245
246        for h in handles {
247            match h.await {
248                Ok(msg) => {
249                    if msg.contains("] OK ") { ok += 1; } else { fail += 1; }
250                    println!("{msg}");
251                }
252                Err(e) => { println!("  task panic: {e}"); fail += 1; }
253            }
254        }
255
256        println!();
257    }
258
259    let elapsed = start.elapsed().as_secs_f64();
260    println!("=== OK:{ok} FAIL:{fail} | {elapsed:.0}s total | {:.1}s/profile ===",
261        elapsed / total as f64);
262}

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more