Skip to main content

InteractiveElement

Struct InteractiveElement 

Source
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: bool

Implementations§

Source§

impl InteractiveElement

Source

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
Hide additional 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

Source§

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)

Performs copy-assignment from source. Read more
Source§

impl Debug for InteractiveElement

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for InteractiveElement

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Serialize for InteractiveElement

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Conv for T

Source§

fn conv<T>(self) -> T
where Self: Into<T>,

Converts self into T using Into<T>. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> ErasedDestructor for T
where T: 'static,

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> MaybeSendSync for T
where T: Send + Sync,

Source§

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

Source§

fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> R
where 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) -> R
where R: 'a,

Borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> R
where R: 'a,

Mutably borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
where Self: Borrow<B>, B: 'a + ?Sized, R: 'a,

Borrows self, then passes self.borrow() into the pipe function. Read more
Source§

fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
where Self: BorrowMut<B>, B: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.borrow_mut() into the pipe function. Read more
Source§

fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
where Self: AsRef<U>, U: 'a + ?Sized, R: 'a,

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
where Self: AsMut<U>, U: 'a + ?Sized, R: 'a,

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
where Self: Deref<Target = T>, T: 'a + ?Sized, R: 'a,

Borrows self, then passes self.deref() into the pipe function.
Source§

fn pipe_deref_mut<'a, T, R>( &'a mut self, func: impl FnOnce(&'a mut T) -> R, ) -> R
where Self: DerefMut<Target = T> + Deref, T: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.deref_mut() into the pipe function.
Source§

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

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + 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: Sized + 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> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Tap for T

Source§

fn tap(self, func: impl FnOnce(&Self)) -> Self

Immutable access to a value. Read more
Source§

fn tap_mut(self, func: impl FnOnce(&mut Self)) -> Self

Mutable access to a value. Read more
Source§

fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Immutable access to the Borrow<B> of a value. Read more
Source§

fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Mutable access to the BorrowMut<B> of a value. Read more
Source§

fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Immutable access to the AsRef<R> view of a value. Read more
Source§

fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Mutable access to the AsMut<R> view of a value. Read more
Source§

fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Immutable access to the Deref::Target of a value. Read more
Source§

fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Mutable access to the Deref::Target of a value. Read more
Source§

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

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
where Self: Borrow<B>, B: ?Sized,

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
where Self: BorrowMut<B>, B: ?Sized,

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
where Self: AsRef<R>, R: ?Sized,

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
where Self: AsMut<R>, R: ?Sized,

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
where Self: Deref<Target = T>, T: ?Sized,

Calls .tap_deref() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_mut_dbg<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Calls .tap_deref_mut() only in debug builds, and is erased in release builds.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> TryConv for T

Source§

fn try_conv<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Attempts to convert self into T using TryInto<T>. Read more
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