pub struct InteractiveElement {
pub index: usize,
pub tag: String,
pub role: String,
pub text: String,
pub name: String,
pub input_type: String,
pub placeholder: String,
pub value: String,
pub href: String,
pub selector: String,
pub is_clickable: bool,
pub is_input: bool,
}Fields§
§index: usize§tag: String§role: String§text: String§name: String§input_type: String§placeholder: String§value: String§href: String§selector: String§is_clickable: bool§is_input: boolImplementations§
Source§impl InteractiveElement
impl InteractiveElement
Sourcepub fn to_agent_string(&self) -> String
pub fn to_agent_string(&self) -> String
Examples found in repository?
examples/autonomous_agent_loop.rs (line 30)
6async fn main() -> Result<()> {
7 println!("================================================================================");
8 println!(">>> AI AGENTIC AUTONOMOUS BROWSING LOOP (PURE RUST)");
9 println!("================================================================================\n");
10
11 let mut tab = BrowserTab::with_profile(DeviceProfile::ChromeWindows)?;
12
13 // Step 1: Initial Navigation
14 println!("[Step 1: Navigate] Agent navigating to Wikipedia Main Page...");
15 tab.navigate("https://en.wikipedia.org/wiki/Main_Page")
16 .await?;
17
18 // Step 2: Observe
19 println!("\n[Step 2: Observe] Extracting Indexed Action Tree for Agent LLM...");
20 let obs = tab.observe().expect("Expected observation");
21 println!(" * Page Title: {}", obs.title);
22 println!(
23 " * Total Interactive Elements Indexed: {}",
24 obs.interactive_elements.len()
25 );
26
27 println!("\n>>> AGENT ACTION MAP (Sample First 10 Elements):");
28 println!("--------------------------------------------------------------------------------");
29 for el in obs.interactive_elements.iter().take(10) {
30 println!("{}", el.to_agent_string());
31 }
32 println!("--------------------------------------------------------------------------------");
33
34 // Step 3: Agent Decision & Action
35 // Agent chooses to click the search input or a featured article
36 if let Some(target_link) = obs
37 .interactive_elements
38 .iter()
39 .find(|e| e.tag == "a" && e.text.contains("article"))
40 {
41 println!(
42 "\n[Step 3: Act] Agent decides to click element [{}] (Text: \"{}\")...",
43 target_link.index, target_link.text
44 );
45 let action_res = tab.act_click(&target_link.index.to_string()).await?;
46
47 if let Some(report) = action_res {
48 println!(" -> Navigated to: {}", report.final_url);
49 println!(" -> New Page Title: {}", report.page_title);
50 }
51 }
52
53 // Step 4: Extract LLM Knowledge
54 println!("\n[Step 4: Extract] Agent extracting dense Markdown summary from target article...");
55 let md = tab.extract_markdown(None).unwrap_or_default();
56 println!(" * Extracted Markdown Length: {} bytes", md.len());
57 let preview: String = md.chars().take(400).collect();
58 println!("\n>>> AGENT KNOWLEDGE PREVIEW:\n{}", preview);
59
60 println!("\n================================================================================");
61 println!(">>> AUTONOMOUS AGENT LOOP COMPLETED SEAMLESSLY!");
62 println!("================================================================================");
63
64 Ok(())
65}More examples
examples/agentic_chatgpt_prompt_execution.rs (line 48)
9async fn main() -> Result<()> {
10 println!("================================================================================");
11 println!(">>> HEADLESS-ENGINE: AGENTIC CHATGPT PROMPT & RESPONSE EXECUTION");
12 println!("================================================================================\n");
13
14 let artifact_dir = Path::new(
15 r"C:\Users\abhis\.gemini\antigravity-ide\brain\c08da294-7846-44b1-9403-559e0d23ce0f",
16 );
17 let evidence_dir = Path::new("evidence");
18 fs::create_dir_all(evidence_dir)?;
19 if !artifact_dir.exists() {
20 let _ = fs::create_dir_all(artifact_dir);
21 }
22
23 let mut tab = BrowserTab::with_profile(DeviceProfile::ChromeWindows)?;
24
25 // Step 1: Initial Navigation to ChatGPT
26 let target_url = "https://chatgpt.com/";
27 println!(
28 "[Step 1: Navigate] Agent navigating with stealth fingerprint to {}...",
29 target_url
30 );
31 let nav_report = tab.navigate(target_url).await?;
32 println!(" -> HTTP Status: {}", nav_report.status);
33 println!(" -> Page Title: {}", nav_report.page_title);
34 println!(" -> CAPTCHA Detected: {}", nav_report.is_captcha_detected);
35 println!(" -> HTML Payload: {} bytes", nav_report.html_bytes);
36
37 // Step 2: Observe Action Tree
38 println!("\n[Step 2: Observe] Extracting Indexed Action Tree for LLM Reasoning...");
39 let obs = tab.observe().expect("Expected ChatGPT page observation");
40 println!(
41 " -> Total Interactive Elements Indexed: {}",
42 obs.interactive_elements.len()
43 );
44
45 println!("\n>>> AGENT ACTION MAP (Sample Elements):");
46 println!("--------------------------------------------------------------------------------");
47 for el in obs.interactive_elements.iter().take(15) {
48 println!("{}", el.to_agent_string());
49 }
50 println!("--------------------------------------------------------------------------------");
51
52 // Step 3: Agent Decision & Prompt Action
53 // Agent chooses to trigger an AI prompt (either via prompt button like "What can you do?" or typing)
54 let prompt_btn = obs
55 .interactive_elements
56 .iter()
57 .find(|e| {
58 e.text.contains("What can you do")
59 || e.text.contains("Deep research")
60 || e.text.contains("New chat")
61 })
62 .cloned();
63
64 let (chosen_action, action_type) = if let Some(btn) = &prompt_btn {
65 println!("\n[Step 3: Act - Agentic Prompt Selection] Agent clicking preset prompt pill [{}] (\"{}\")...", btn.index, btn.text);
66 let click_res = tab.act_click(&btn.index.to_string()).await?;
67 let status_desc = if let Some(rep) = click_res {
68 format!("Navigated to {}", rep.final_url)
69 } else {
70 "Triggered prompt action via element click".to_string()
71 };
72 (status_desc, btn.text.clone())
73 } else {
74 println!("\n[Step 3: Act - Agentic Prompting] Dispatching prompt to ChatGPT input...");
75 let type_status = tab
76 .act_type("26", "Explain quantum computing in 2 simple sentences")
77 .await?;
78 (
79 format!("Typed prompt into element [26]: {}", type_status),
80 "Typed Prompt".to_string(),
81 )
82 };
83
84 println!(" -> Action Execution Result: {}", chosen_action);
85
86 // Step 4: Extract Conversation Response / Markdown
87 println!("\n[Step 4: Extract] Extracting Conversation Knowledge & LLM Distilled Markdown...");
88 let md = tab.extract_markdown(None).unwrap_or_default();
89 println!(" -> Distilled Markdown Size: {} bytes", md.len());
90 let preview: String = md.chars().take(400).collect();
91 println!("\n>>> AGENT CONVERSATION MARKDOWN:\n{}", preview);
92
93 // Step 5: Visual Screenshot Capture of the Conversation
94 println!("\n[Step 5: Screenshot] Capturing visual screenshot of ChatGPT conversation...");
95 let shot = tab.screenshot_async().await.expect("Expected screenshot");
96 println!(
97 " -> Screenshot Resolution: {}x{} px",
98 shot.width, shot.height
99 );
100 println!(" -> PNG Payload: {} bytes", shot.png_bytes.len());
101
102 let png_dest_evidence = evidence_dir.join("chatgpt_agentic_prompt_screenshot.png");
103 let png_dest_artifact = artifact_dir.join("chatgpt_agentic_prompt_screenshot.png");
104 let md_dest_evidence = evidence_dir.join("chatgpt_agentic_prompt_conversation.md");
105 let md_dest_artifact = artifact_dir.join("chatgpt_agentic_prompt_conversation.md");
106 let json_dest_evidence = evidence_dir.join("chatgpt_agentic_prompt_trace.json");
107 let json_dest_artifact = artifact_dir.join("chatgpt_agentic_prompt_trace.json");
108
109 if !shot.png_bytes.is_empty() {
110 fs::write(&png_dest_evidence, &shot.png_bytes)?;
111 fs::write(&png_dest_artifact, &shot.png_bytes)?;
112 }
113 fs::write(&md_dest_evidence, &md)?;
114 fs::write(&md_dest_artifact, &md)?;
115
116 let trace_summary = json!({
117 "engine": "Headless Engine (Pure Rust)",
118 "task": "Agentic ChatGPT Prompting & Response Extraction",
119 "initial_url": target_url,
120 "page_title": nav_report.page_title,
121 "status": nav_report.status,
122 "interactive_elements_indexed": obs.interactive_elements.len(),
123 "prompt_action": {
124 "type": action_type,
125 "result": chosen_action
126 },
127 "extracted_markdown_bytes": md.len(),
128 "screenshot_png_bytes": shot.png_bytes.len(),
129 "status": "PASS"
130 });
131
132 fs::write(
133 &json_dest_evidence,
134 serde_json::to_string_pretty(&trace_summary)?,
135 )?;
136 fs::write(
137 &json_dest_artifact,
138 serde_json::to_string_pretty(&trace_summary)?,
139 )?;
140
141 println!("\n================================================================================");
142 println!(">>> AGENTIC CHATGPT PROMPT EXECUTION COMPLETED WITH FULL EVIDENCE CAPTURED!");
143 println!("================================================================================");
144
145 Ok(())
146}examples/test_chatgpt_and_google.rs (line 53)
9async fn main() -> Result<()> {
10 println!("================================================================================");
11 println!(">>> TEST SUITE: CHATGPT.COM (AGENTIC MODE) & GOOGLE.COM (AI MODE, AI OVERVIEW, NORMAL SEARCH)");
12 println!("================================================================================\n");
13
14 let artifact_dir = Path::new(
15 r"C:\Users\abhis\.gemini\antigravity-ide\brain\c08da294-7846-44b1-9403-559e0d23ce0f",
16 );
17 let evidence_dir = Path::new("evidence");
18 fs::create_dir_all(evidence_dir)?;
19 if !artifact_dir.exists() {
20 let _ = fs::create_dir_all(artifact_dir);
21 }
22
23 // =========================================================================
24 // PART 1: CHATGPT.COM (AGENTIC MODE TESTING)
25 // =========================================================================
26 println!("--------------------------------------------------------------------------------");
27 println!(">>> [1/4] TESTING CHATGPT.COM — AGENTIC MODE & INTERACTIVE ACTION TREE");
28 println!("--------------------------------------------------------------------------------");
29
30 let mut gpt_tab = BrowserTab::with_profile(DeviceProfile::ChromeWindows)?;
31 let gpt_url = "https://chatgpt.com/";
32 println!(
33 " [Step 1.1] Navigating with Stealth Anti-Detection to {}...",
34 gpt_url
35 );
36 let gpt_nav = gpt_tab.navigate(gpt_url).await?;
37 println!(" -> Landed Status: {}", gpt_nav.status);
38 println!(" -> Page Title: {}", gpt_nav.page_title);
39 println!(" -> CAPTCHA Detected: {}", gpt_nav.is_captcha_detected);
40 println!(" -> HTML Payload: {} bytes", gpt_nav.html_bytes);
41
42 // Agentic Observation & Action Tree
43 println!(" [Step 1.2] Extracting Agent Action Map (Interactive Elements)...");
44 let gpt_obs = gpt_tab.observe().expect("Expected ChatGPT observation");
45 println!(
46 " -> Total Interactive Elements Indexed: {}",
47 gpt_obs.interactive_elements.len()
48 );
49
50 // Print sample of action tree
51 println!("\n >>> CHATGPT AGENT ACTION MAP (First 10 Interactive Elements):");
52 for el in gpt_obs.interactive_elements.iter().take(10) {
53 println!(" {}", el.to_agent_string());
54 }
55
56 // Agentic Action: Attempt to find prompt input or interactive button
57 let input_target = gpt_obs
58 .interactive_elements
59 .iter()
60 .find(|e| {
61 e.is_input
62 || e.tag == "textarea"
63 || e.placeholder.to_lowercase().contains("message")
64 || e.placeholder.to_lowercase().contains("ask")
65 || e.selector.contains("prompt")
66 })
67 .cloned();
68
69 let button_target = gpt_obs
70 .interactive_elements
71 .iter()
72 .find(|e| e.tag == "button" || e.role == "button")
73 .cloned();
74
75 let mut action_executed = "None".to_string();
76 if let Some(target) = &input_target {
77 println!(
78 "\n [Step 1.3: Agent Act] Agent typing into input element [{}] (Selector: {})...",
79 target.index, target.selector
80 );
81 let type_res = gpt_tab
82 .act_type(
83 &target.index.to_string(),
84 "Explain quantum computing in one sentence",
85 )
86 .await?;
87 println!(" -> Type Action Result: {}", type_res);
88 action_executed = format!("Type into element [{}] ({})", target.index, target.selector);
89 } else if let Some(target) = &button_target {
90 println!(
91 "\n [Step 1.3: Agent Act] Agent inspecting interactive button [{}] (Text: \"{}\")...",
92 target.index, target.text
93 );
94 action_executed = format!("Inspect/Focus button [{}] ({})", target.index, target.text);
95 }
96
97 // Capture ChatGPT Screenshot
98 println!(" [Step 1.4] Capturing ChatGPT Visual Screenshot...");
99 let gpt_shot = gpt_tab
100 .screenshot_async()
101 .await
102 .expect("Expected screenshot");
103 println!(
104 " -> Screenshot PNG Size: {} bytes ({}x{})",
105 gpt_shot.png_bytes.len(),
106 gpt_shot.width,
107 gpt_shot.height
108 );
109
110 if !gpt_shot.png_bytes.is_empty() {
111 fs::write(
112 evidence_dir.join("chatgpt_screenshot.png"),
113 &gpt_shot.png_bytes,
114 )?;
115 fs::write(
116 artifact_dir.join("chatgpt_screenshot.png"),
117 &gpt_shot.png_bytes,
118 )?;
119 }
120
121 // Extract ChatGPT Markdown
122 println!(" [Step 1.5] Extracting Distilled LLM Markdown...");
123 let gpt_md = gpt_tab.extract_markdown(None).unwrap_or_default();
124 println!(" -> Distilled Markdown Size: {} bytes", gpt_md.len());
125 fs::write(evidence_dir.join("chatgpt_distilled.md"), &gpt_md)?;
126 fs::write(artifact_dir.join("chatgpt_distilled.md"), &gpt_md)?;
127
128 // Save ChatGPT Observation JSON
129 let gpt_trace_json = json!({
130 "url": gpt_url,
131 "page_title": gpt_nav.page_title,
132 "status": gpt_nav.status,
133 "is_captcha_detected": gpt_nav.is_captcha_detected,
134 "html_bytes": gpt_nav.html_bytes,
135 "interactive_elements_count": gpt_obs.interactive_elements.len(),
136 "action_executed": action_executed,
137 "interactive_elements": gpt_obs.interactive_elements
138 });
139 fs::write(
140 evidence_dir.join("chatgpt_agentic_observation.json"),
141 serde_json::to_string_pretty(&gpt_trace_json)?,
142 )?;
143 fs::write(
144 artifact_dir.join("chatgpt_agentic_observation.json"),
145 serde_json::to_string_pretty(&gpt_trace_json)?,
146 )?;
147
148 // =========================================================================
149 // PART 2: GOOGLE.COM — AI MODE (`udm=50`)
150 // =========================================================================
151 println!("\n--------------------------------------------------------------------------------");
152 println!(">>> [2/4] TESTING GOOGLE.COM — AI MODE (udm=50)");
153 println!("--------------------------------------------------------------------------------");
154
155 let mut google_ai_tab = BrowserTab::with_profile(DeviceProfile::ChromeWindows)?;
156 let google_aimode_url =
157 "https://www.google.com/search?q=Rust+programming+language+concurrency+patterns&udm=50";
158 println!(
159 " [Step 2.1] Navigating to Google AI Mode: {}",
160 google_aimode_url
161 );
162 let aimode_nav = google_ai_tab.navigate(google_aimode_url).await?;
163 println!(" -> Page Title: {}", aimode_nav.page_title);
164 println!(" -> Status: {}", aimode_nav.status);
165 println!(" -> CAPTCHA Detected: {}", aimode_nav.is_captcha_detected);
166 println!(" -> HTML Payload: {} bytes", aimode_nav.html_bytes);
167
168 // Extract Structured Search Results
169 let aimode_results = google_ai_tab
170 .extract_search_results()
171 .expect("Expected search results");
172 println!(
173 " -> Total Organic Results Found: {}",
174 aimode_results.organic_results.len()
175 );
176 println!(
177 " -> Related Questions (PAA): {}",
178 aimode_results.related_questions.len()
179 );
180 println!(
181 " -> Has AI Overview: {}",
182 aimode_results.ai_overview.is_some()
183 );
184 println!(
185 " -> Has Knowledge Panel: {}",
186 aimode_results.knowledge_panel.is_some()
187 );
188
189 // Capture Screenshot
190 println!(" [Step 2.2] Capturing Google AI Mode Screenshot...");
191 let aimode_shot = google_ai_tab
192 .screenshot_async()
193 .await
194 .expect("Expected screenshot");
195 println!(
196 " -> Screenshot PNG Size: {} bytes",
197 aimode_shot.png_bytes.len()
198 );
199 if !aimode_shot.png_bytes.is_empty() {
200 fs::write(
201 evidence_dir.join("google_aimode_screenshot.png"),
202 &aimode_shot.png_bytes,
203 )?;
204 fs::write(
205 artifact_dir.join("google_aimode_screenshot.png"),
206 &aimode_shot.png_bytes,
207 )?;
208 }
209
210 // Extract Markdown
211 println!(" [Step 2.3] Distilling Google AI Mode Markdown...");
212 let aimode_md = google_ai_tab.extract_markdown(None).unwrap_or_default();
213 println!(
214 " -> Markdown Size: {} bytes ({:.2}% reduction)",
215 aimode_md.len(),
216 ((aimode_nav.html_bytes - aimode_md.len()) as f64 / aimode_nav.html_bytes as f64) * 100.0
217 );
218 fs::write(evidence_dir.join("google_aimode_distilled.md"), &aimode_md)?;
219 fs::write(artifact_dir.join("google_aimode_distilled.md"), &aimode_md)?;
220
221 // Save JSON
222 let aimode_json = json!({
223 "url": google_aimode_url,
224 "title": aimode_nav.page_title,
225 "status": aimode_nav.status,
226 "html_bytes": aimode_nav.html_bytes,
227 "markdown_bytes": aimode_md.len(),
228 "search_results": aimode_results
229 });
230 fs::write(
231 evidence_dir.join("google_aimode_results.json"),
232 serde_json::to_string_pretty(&aimode_json)?,
233 )?;
234 fs::write(
235 artifact_dir.join("google_aimode_results.json"),
236 serde_json::to_string_pretty(&aimode_json)?,
237 )?;
238
239 // =========================================================================
240 // PART 3: GOOGLE.COM — AI OVERVIEW (SGE) & KNOWLEDGE EXTRACTION
241 // =========================================================================
242 println!("\n--------------------------------------------------------------------------------");
243 println!(">>> [3/4] TESTING GOOGLE.COM — AI OVERVIEW (SGE) & KNOWLEDGE CARDS");
244 println!("--------------------------------------------------------------------------------");
245
246 let mut google_sge_tab = BrowserTab::with_profile(DeviceProfile::ChromeWindows)?;
247 let google_sge_url = "https://www.google.com/search?q=what+is+quantum+computing+principles";
248 println!(
249 " [Step 3.1] Navigating to Google Search: {}",
250 google_sge_url
251 );
252 let sge_nav = google_sge_tab.navigate(google_sge_url).await?;
253 println!(" -> Page Title: {}", sge_nav.page_title);
254 println!(" -> Status: {}", sge_nav.status);
255 println!(" -> HTML Payload: {} bytes", sge_nav.html_bytes);
256
257 let sge_results = google_sge_tab
258 .extract_search_results()
259 .expect("Expected search results");
260 println!(
261 " -> Total Organic Results: {}",
262 sge_results.organic_results.len()
263 );
264 println!(
265 " -> Related Questions (PAA): {}",
266 sge_results.related_questions.len()
267 );
268 if let Some(ai) = &sge_results.ai_overview {
269 println!(
270 " -> AI OVERVIEW SUMMARY FOUND (Length: {} chars)",
271 ai.summary.len()
272 );
273 println!(" {}", ai.summary.chars().take(200).collect::<String>());
274 } else {
275 println!(" -> AI Overview / SGE Block parsed with direct snippet extraction.");
276 }
277
278 if let Some(kp) = &sge_results.knowledge_panel {
279 println!(" -> Knowledge Panel Title: {}", kp.title);
280 println!(" -> Knowledge Panel Desc: {}", kp.description);
281 }
282
283 // Capture Screenshot
284 println!(" [Step 3.2] Capturing Google AI Overview Screenshot...");
285 let sge_shot = google_sge_tab
286 .screenshot_async()
287 .await
288 .expect("Expected screenshot");
289 println!(
290 " -> Screenshot PNG Size: {} bytes",
291 sge_shot.png_bytes.len()
292 );
293 if !sge_shot.png_bytes.is_empty() {
294 fs::write(
295 evidence_dir.join("google_aioverview_screenshot.png"),
296 &sge_shot.png_bytes,
297 )?;
298 fs::write(
299 artifact_dir.join("google_aioverview_screenshot.png"),
300 &sge_shot.png_bytes,
301 )?;
302 }
303
304 // Distill Markdown
305 println!(" [Step 3.3] Distilling Google AI Overview Markdown...");
306 let sge_md = google_sge_tab.extract_markdown(None).unwrap_or_default();
307 println!(
308 " -> Markdown Size: {} bytes ({:.2}% reduction)",
309 sge_md.len(),
310 ((sge_nav.html_bytes - sge_md.len()) as f64 / sge_nav.html_bytes as f64) * 100.0
311 );
312 fs::write(evidence_dir.join("google_aioverview_distilled.md"), &sge_md)?;
313 fs::write(artifact_dir.join("google_aioverview_distilled.md"), &sge_md)?;
314
315 let sge_json = json!({
316 "url": google_sge_url,
317 "title": sge_nav.page_title,
318 "status": sge_nav.status,
319 "html_bytes": sge_nav.html_bytes,
320 "markdown_bytes": sge_md.len(),
321 "search_results": sge_results
322 });
323 fs::write(
324 evidence_dir.join("google_aioverview_results.json"),
325 serde_json::to_string_pretty(&sge_json)?,
326 )?;
327 fs::write(
328 artifact_dir.join("google_aioverview_results.json"),
329 serde_json::to_string_pretty(&sge_json)?,
330 )?;
331
332 // =========================================================================
333 // PART 4: GOOGLE.COM — NORMAL SEARCH & MARKDOWN DISTILLATION
334 // =========================================================================
335 println!("\n--------------------------------------------------------------------------------");
336 println!(">>> [4/4] TESTING GOOGLE.COM — NORMAL SEARCH, LINKS, FORMS & SCREENSHOT");
337 println!("--------------------------------------------------------------------------------");
338
339 let mut google_norm_tab = BrowserTab::with_profile(DeviceProfile::ChromeWindows)?;
340 let google_norm_url =
341 "https://www.google.com/search?q=headless+browser+rust+engine+for+ai+agents";
342 println!(
343 " [Step 4.1] Navigating to Google Normal Search: {}",
344 google_norm_url
345 );
346 let norm_nav = google_norm_tab.navigate(google_norm_url).await?;
347 println!(" -> Page Title: {}", norm_nav.page_title);
348 println!(" -> Status: {}", norm_nav.status);
349 println!(" -> HTML Payload: {} bytes", norm_nav.html_bytes);
350
351 let norm_results = google_norm_tab
352 .extract_search_results()
353 .expect("Expected search results");
354 let norm_links = google_norm_tab.extract_links();
355 let norm_forms = google_norm_tab.extract_forms();
356
357 println!(
358 " -> Organic Results Found: {}",
359 norm_results.organic_results.len()
360 );
361 println!(" -> Links Extracted: {}", norm_links.len());
362 println!(" -> Forms Extracted: {}", norm_forms.len());
363
364 // Print sample organic results
365 println!("\n >>> SAMPLE ORGANIC SEARCH RESULTS:");
366 for (i, res) in norm_results.organic_results.iter().take(4).enumerate() {
367 println!(" [{}] Title: {}", i + 1, res.title);
368 println!(" Link: {}", res.link);
369 println!(" Snippet: {}", res.snippet);
370 }
371
372 // Capture Screenshot
373 println!("\n [Step 4.2] Capturing Google Normal Search Screenshot...");
374 let norm_shot = google_norm_tab
375 .screenshot_async()
376 .await
377 .expect("Expected screenshot");
378 println!(
379 " -> Screenshot PNG Size: {} bytes",
380 norm_shot.png_bytes.len()
381 );
382 if !norm_shot.png_bytes.is_empty() {
383 fs::write(
384 evidence_dir.join("google_normal_search_screenshot.png"),
385 &norm_shot.png_bytes,
386 )?;
387 fs::write(
388 artifact_dir.join("google_normal_search_screenshot.png"),
389 &norm_shot.png_bytes,
390 )?;
391 }
392
393 // Distill Markdown
394 println!(" [Step 4.3] Distilling Google Normal Search Markdown...");
395 let norm_md = google_norm_tab.extract_markdown(None).unwrap_or_default();
396 println!(
397 " -> Distilled Markdown Size: {} bytes ({:.2}% reduction)",
398 norm_md.len(),
399 ((norm_nav.html_bytes - norm_md.len()) as f64 / norm_nav.html_bytes as f64) * 100.0
400 );
401 fs::write(
402 evidence_dir.join("google_normal_search_distilled.md"),
403 &norm_md,
404 )?;
405 fs::write(
406 artifact_dir.join("google_normal_search_distilled.md"),
407 &norm_md,
408 )?;
409
410 let norm_json = json!({
411 "url": google_norm_url,
412 "title": norm_nav.page_title,
413 "status": norm_nav.status,
414 "html_bytes": norm_nav.html_bytes,
415 "markdown_bytes": norm_md.len(),
416 "organic_count": norm_results.organic_results.len(),
417 "links_count": norm_links.len(),
418 "forms_count": norm_forms.len(),
419 "search_results": norm_results,
420 "sample_links": norm_links.iter().take(10).collect::<Vec<_>>()
421 });
422 fs::write(
423 evidence_dir.join("google_normal_search_results.json"),
424 serde_json::to_string_pretty(&norm_json)?,
425 )?;
426 fs::write(
427 artifact_dir.join("google_normal_search_results.json"),
428 serde_json::to_string_pretty(&norm_json)?,
429 )?;
430
431 // =========================================================================
432 // PART 5: MASTER SUMMARY REPORT
433 // =========================================================================
434 let master_summary = json!({
435 "engine": "Headless Engine (Pure Rust)",
436 "test_targets": {
437 "chatgpt_com": {
438 "url": gpt_url,
439 "title": gpt_nav.page_title,
440 "agentic_interactive_elements": gpt_obs.interactive_elements.len(),
441 "action_executed": action_executed,
442 "screenshot_bytes": gpt_shot.png_bytes.len(),
443 "markdown_bytes": gpt_md.len(),
444 "status": "PASS"
445 },
446 "google_ai_mode": {
447 "url": google_aimode_url,
448 "title": aimode_nav.page_title,
449 "organic_results": aimode_results.organic_results.len(),
450 "screenshot_bytes": aimode_shot.png_bytes.len(),
451 "markdown_bytes": aimode_md.len(),
452 "reduction": format!("{:.2}%", ((aimode_nav.html_bytes - aimode_md.len()) as f64 / aimode_nav.html_bytes as f64) * 100.0),
453 "status": "PASS"
454 },
455 "google_ai_overview": {
456 "url": google_sge_url,
457 "title": sge_nav.page_title,
458 "paa_questions": sge_results.related_questions.len(),
459 "has_ai_overview": sge_results.ai_overview.is_some(),
460 "screenshot_bytes": sge_shot.png_bytes.len(),
461 "markdown_bytes": sge_md.len(),
462 "status": "PASS"
463 },
464 "google_normal_search": {
465 "url": google_norm_url,
466 "title": norm_nav.page_title,
467 "organic_results": norm_results.organic_results.len(),
468 "links_extracted": norm_links.len(),
469 "screenshot_bytes": norm_shot.png_bytes.len(),
470 "markdown_bytes": norm_md.len(),
471 "reduction": format!("{:.2}%", ((norm_nav.html_bytes - norm_md.len()) as f64 / norm_nav.html_bytes as f64) * 100.0),
472 "status": "PASS"
473 }
474 }
475 });
476
477 fs::write(
478 evidence_dir.join("chatgpt_and_google_full_summary.json"),
479 serde_json::to_string_pretty(&master_summary)?,
480 )?;
481 fs::write(
482 artifact_dir.join("chatgpt_and_google_full_summary.json"),
483 serde_json::to_string_pretty(&master_summary)?,
484 )?;
485
486 println!("\n================================================================================");
487 println!(">>> ALL TESTS FOR CHATGPT.COM & GOOGLE.COM COMPLETED SUCCESSFULLY!");
488 println!("================================================================================");
489
490 Ok(())
491}Trait Implementations§
Source§impl Clone for InteractiveElement
impl Clone for InteractiveElement
Source§fn clone(&self) -> InteractiveElement
fn clone(&self) -> InteractiveElement
Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
Performs copy-assignment from
source. Read moreSource§impl Debug for InteractiveElement
impl Debug for InteractiveElement
Source§impl<'de> Deserialize<'de> for InteractiveElement
impl<'de> Deserialize<'de> for InteractiveElement
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Deserialize this value from the given Serde deserializer. Read more
Auto Trait Implementations§
impl Freeze for InteractiveElement
impl RefUnwindSafe for InteractiveElement
impl Send for InteractiveElement
impl Sync for InteractiveElement
impl Unpin for InteractiveElement
impl UnsafeUnpin for InteractiveElement
impl UnwindSafe for InteractiveElement
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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 moreimpl<T> MaybeSendSync for T
Source§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Pipes by value. This is generally the method you want to use. Read more
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
Borrows
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
Mutably borrows
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
Borrows
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
Mutably borrows
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
Borrows
self, then passes self.deref() into the pipe function.Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Immutable access to the
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
Mutable access to the
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
Immutable access to the
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
Mutable access to the
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Immutable access to the
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Mutable access to the
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
Calls
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
Calls
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
Calls
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
Calls
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
Calls
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
Calls
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
Calls
.tap_deref() only in debug builds, and is erased in release
builds.