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 40)
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(r"C:\Users\abhis\.gemini\antigravity-ide\brain\c08da294-7846-44b1-9403-559e0d23ce0f");
15    let evidence_dir = Path::new("evidence");
16    fs::create_dir_all(evidence_dir)?;
17    if !artifact_dir.exists() {
18        let _ = fs::create_dir_all(artifact_dir);
19    }
20
21    let mut tab = BrowserTab::with_profile(DeviceProfile::ChromeWindows)?;
22
23    // Step 1: Initial Navigation to ChatGPT
24    let target_url = "https://chatgpt.com/";
25    println!("[Step 1: Navigate] Agent navigating with stealth fingerprint to {}...", target_url);
26    let nav_report = tab.navigate(target_url).await?;
27    println!("  -> HTTP Status:      {}", nav_report.status);
28    println!("  -> Page Title:       {}", nav_report.page_title);
29    println!("  -> CAPTCHA Detected: {}", nav_report.is_captcha_detected);
30    println!("  -> HTML Payload:     {} bytes", nav_report.html_bytes);
31
32    // Step 2: Observe Action Tree
33    println!("\n[Step 2: Observe] Extracting Indexed Action Tree for LLM Reasoning...");
34    let obs = tab.observe().expect("Expected ChatGPT page observation");
35    println!("  -> Total Interactive Elements Indexed: {}", obs.interactive_elements.len());
36
37    println!("\n>>> AGENT ACTION MAP (Sample Elements):");
38    println!("--------------------------------------------------------------------------------");
39    for el in obs.interactive_elements.iter().take(15) {
40        println!("{}", el.to_agent_string());
41    }
42    println!("--------------------------------------------------------------------------------");
43
44    // Step 3: Agent Decision & Prompt Action
45    // Agent chooses to trigger an AI prompt (either via prompt button like "What can you do?" or typing)
46    let prompt_btn = obs
47        .interactive_elements
48        .iter()
49        .find(|e| e.text.contains("What can you do") || e.text.contains("Deep research") || e.text.contains("New chat"))
50        .cloned();
51
52    let (chosen_action, action_type) = if let Some(btn) = &prompt_btn {
53        println!("\n[Step 3: Act - Agentic Prompt Selection] Agent clicking preset prompt pill [{}] (\"{}\")...", btn.index, btn.text);
54        let click_res = tab.act_click(&btn.index.to_string()).await?;
55        let status_desc = if let Some(rep) = click_res {
56            format!("Navigated to {}", rep.final_url)
57        } else {
58            "Triggered prompt action via element click".to_string()
59        };
60        (status_desc, btn.text.clone())
61    } else {
62        println!("\n[Step 3: Act - Agentic Prompting] Dispatching prompt to ChatGPT input...");
63        let type_status = tab.act_type("26", "Explain quantum computing in 2 simple sentences").await?;
64        (format!("Typed prompt into element [26]: {}", type_status), "Typed Prompt".to_string())
65    };
66
67    println!("  -> Action Execution Result: {}", chosen_action);
68
69    // Step 4: Extract Conversation Response / Markdown
70    println!("\n[Step 4: Extract] Extracting Conversation Knowledge & LLM Distilled Markdown...");
71    let md = tab.extract_markdown(None).unwrap_or_default();
72    println!("  -> Distilled Markdown Size: {} bytes", md.len());
73    let preview: String = md.chars().take(400).collect();
74    println!("\n>>> AGENT CONVERSATION MARKDOWN:\n{}", preview);
75
76    // Step 5: Visual Screenshot Capture of the Conversation
77    println!("\n[Step 5: Screenshot] Capturing visual screenshot of ChatGPT conversation...");
78    let shot = tab.screenshot_async().await.expect("Expected screenshot");
79    println!("  -> Screenshot Resolution: {}x{} px", shot.width, shot.height);
80    println!("  -> PNG Payload:          {} bytes", shot.png_bytes.len());
81
82    let png_dest_evidence = evidence_dir.join("chatgpt_agentic_prompt_screenshot.png");
83    let png_dest_artifact = artifact_dir.join("chatgpt_agentic_prompt_screenshot.png");
84    let md_dest_evidence = evidence_dir.join("chatgpt_agentic_prompt_conversation.md");
85    let md_dest_artifact = artifact_dir.join("chatgpt_agentic_prompt_conversation.md");
86    let json_dest_evidence = evidence_dir.join("chatgpt_agentic_prompt_trace.json");
87    let json_dest_artifact = artifact_dir.join("chatgpt_agentic_prompt_trace.json");
88
89    if !shot.png_bytes.is_empty() {
90        fs::write(&png_dest_evidence, &shot.png_bytes)?;
91        fs::write(&png_dest_artifact, &shot.png_bytes)?;
92    }
93    fs::write(&md_dest_evidence, &md)?;
94    fs::write(&md_dest_artifact, &md)?;
95
96    let trace_summary = json!({
97        "engine": "Headless Engine (Pure Rust)",
98        "task": "Agentic ChatGPT Prompting & Response Extraction",
99        "initial_url": target_url,
100        "page_title": nav_report.page_title,
101        "status": nav_report.status,
102        "interactive_elements_indexed": obs.interactive_elements.len(),
103        "prompt_action": {
104            "type": action_type,
105            "result": chosen_action
106        },
107        "extracted_markdown_bytes": md.len(),
108        "screenshot_png_bytes": shot.png_bytes.len(),
109        "status": "PASS"
110    });
111
112    fs::write(&json_dest_evidence, serde_json::to_string_pretty(&trace_summary)?)?;
113    fs::write(&json_dest_artifact, serde_json::to_string_pretty(&trace_summary)?)?;
114
115    println!("\n================================================================================");
116    println!(">>> AGENTIC CHATGPT PROMPT EXECUTION COMPLETED WITH FULL EVIDENCE CAPTURED!");
117    println!("================================================================================");
118
119    Ok(())
120}
examples/test_chatgpt_and_google.rs (line 45)
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(r"C:\Users\abhis\.gemini\antigravity-ide\brain\c08da294-7846-44b1-9403-559e0d23ce0f");
15    let evidence_dir = Path::new("evidence");
16    fs::create_dir_all(evidence_dir)?;
17    if !artifact_dir.exists() {
18        let _ = fs::create_dir_all(artifact_dir);
19    }
20
21    // =========================================================================
22    // PART 1: CHATGPT.COM (AGENTIC MODE TESTING)
23    // =========================================================================
24    println!("--------------------------------------------------------------------------------");
25    println!(">>> [1/4] TESTING CHATGPT.COM — AGENTIC MODE & INTERACTIVE ACTION TREE");
26    println!("--------------------------------------------------------------------------------");
27
28    let mut gpt_tab = BrowserTab::with_profile(DeviceProfile::ChromeWindows)?;
29    let gpt_url = "https://chatgpt.com/";
30    println!("  [Step 1.1] Navigating with Stealth Anti-Detection to {}...", gpt_url);
31    let gpt_nav = gpt_tab.navigate(gpt_url).await?;
32    println!("  -> Landed Status:    {}", gpt_nav.status);
33    println!("  -> Page Title:       {}", gpt_nav.page_title);
34    println!("  -> CAPTCHA Detected: {}", gpt_nav.is_captcha_detected);
35    println!("  -> HTML Payload:     {} bytes", gpt_nav.html_bytes);
36
37    // Agentic Observation & Action Tree
38    println!("  [Step 1.2] Extracting Agent Action Map (Interactive Elements)...");
39    let gpt_obs = gpt_tab.observe().expect("Expected ChatGPT observation");
40    println!("  -> Total Interactive Elements Indexed: {}", gpt_obs.interactive_elements.len());
41
42    // Print sample of action tree
43    println!("\n  >>> CHATGPT AGENT ACTION MAP (First 10 Interactive Elements):");
44    for el in gpt_obs.interactive_elements.iter().take(10) {
45        println!("    {}", el.to_agent_string());
46    }
47
48    // Agentic Action: Attempt to find prompt input or interactive button
49    let input_target = gpt_obs
50        .interactive_elements
51        .iter()
52        .find(|e| e.is_input || e.tag == "textarea" || e.placeholder.to_lowercase().contains("message") || e.placeholder.to_lowercase().contains("ask") || e.selector.contains("prompt"))
53        .cloned();
54
55    let button_target = gpt_obs
56        .interactive_elements
57        .iter()
58        .find(|e| e.tag == "button" || e.role == "button")
59        .cloned();
60
61    let mut action_executed = "None".to_string();
62    if let Some(target) = &input_target {
63        println!("\n  [Step 1.3: Agent Act] Agent typing into input element [{}] (Selector: {})...", target.index, target.selector);
64        let type_res = gpt_tab.act_type(&target.index.to_string(), "Explain quantum computing in one sentence").await?;
65        println!("  -> Type Action Result: {}", type_res);
66        action_executed = format!("Type into element [{}] ({})", target.index, target.selector);
67    } else if let Some(target) = &button_target {
68        println!("\n  [Step 1.3: Agent Act] Agent inspecting interactive button [{}] (Text: \"{}\")...", target.index, target.text);
69        action_executed = format!("Inspect/Focus button [{}] ({})", target.index, target.text);
70    }
71
72    // Capture ChatGPT Screenshot
73    println!("  [Step 1.4] Capturing ChatGPT Visual Screenshot...");
74    let gpt_shot = gpt_tab.screenshot_async().await.expect("Expected screenshot");
75    println!("  -> Screenshot PNG Size: {} bytes ({}x{})", gpt_shot.png_bytes.len(), gpt_shot.width, gpt_shot.height);
76
77    if !gpt_shot.png_bytes.is_empty() {
78        fs::write(evidence_dir.join("chatgpt_screenshot.png"), &gpt_shot.png_bytes)?;
79        fs::write(artifact_dir.join("chatgpt_screenshot.png"), &gpt_shot.png_bytes)?;
80    }
81
82    // Extract ChatGPT Markdown
83    println!("  [Step 1.5] Extracting Distilled LLM Markdown...");
84    let gpt_md = gpt_tab.extract_markdown(None).unwrap_or_default();
85    println!("  -> Distilled Markdown Size: {} bytes", gpt_md.len());
86    fs::write(evidence_dir.join("chatgpt_distilled.md"), &gpt_md)?;
87    fs::write(artifact_dir.join("chatgpt_distilled.md"), &gpt_md)?;
88
89    // Save ChatGPT Observation JSON
90    let gpt_trace_json = json!({
91        "url": gpt_url,
92        "page_title": gpt_nav.page_title,
93        "status": gpt_nav.status,
94        "is_captcha_detected": gpt_nav.is_captcha_detected,
95        "html_bytes": gpt_nav.html_bytes,
96        "interactive_elements_count": gpt_obs.interactive_elements.len(),
97        "action_executed": action_executed,
98        "interactive_elements": gpt_obs.interactive_elements
99    });
100    fs::write(evidence_dir.join("chatgpt_agentic_observation.json"), serde_json::to_string_pretty(&gpt_trace_json)?)?;
101    fs::write(artifact_dir.join("chatgpt_agentic_observation.json"), serde_json::to_string_pretty(&gpt_trace_json)?)?;
102
103
104    // =========================================================================
105    // PART 2: GOOGLE.COM — AI MODE (`udm=50`)
106    // =========================================================================
107    println!("\n--------------------------------------------------------------------------------");
108    println!(">>> [2/4] TESTING GOOGLE.COM — AI MODE (udm=50)");
109    println!("--------------------------------------------------------------------------------");
110
111    let mut google_ai_tab = BrowserTab::with_profile(DeviceProfile::ChromeWindows)?;
112    let google_aimode_url = "https://www.google.com/search?q=Rust+programming+language+concurrency+patterns&udm=50";
113    println!("  [Step 2.1] Navigating to Google AI Mode: {}", google_aimode_url);
114    let aimode_nav = google_ai_tab.navigate(google_aimode_url).await?;
115    println!("  -> Page Title:       {}", aimode_nav.page_title);
116    println!("  -> Status:           {}", aimode_nav.status);
117    println!("  -> CAPTCHA Detected: {}", aimode_nav.is_captcha_detected);
118    println!("  -> HTML Payload:     {} bytes", aimode_nav.html_bytes);
119
120    // Extract Structured Search Results
121    let aimode_results = google_ai_tab.extract_search_results().expect("Expected search results");
122    println!("  -> Total Organic Results Found: {}", aimode_results.organic_results.len());
123    println!("  -> Related Questions (PAA):    {}", aimode_results.related_questions.len());
124    println!("  -> Has AI Overview:            {}", aimode_results.ai_overview.is_some());
125    println!("  -> Has Knowledge Panel:        {}", aimode_results.knowledge_panel.is_some());
126
127    // Capture Screenshot
128    println!("  [Step 2.2] Capturing Google AI Mode Screenshot...");
129    let aimode_shot = google_ai_tab.screenshot_async().await.expect("Expected screenshot");
130    println!("  -> Screenshot PNG Size: {} bytes", aimode_shot.png_bytes.len());
131    if !aimode_shot.png_bytes.is_empty() {
132        fs::write(evidence_dir.join("google_aimode_screenshot.png"), &aimode_shot.png_bytes)?;
133        fs::write(artifact_dir.join("google_aimode_screenshot.png"), &aimode_shot.png_bytes)?;
134    }
135
136    // Extract Markdown
137    println!("  [Step 2.3] Distilling Google AI Mode Markdown...");
138    let aimode_md = google_ai_tab.extract_markdown(None).unwrap_or_default();
139    println!("  -> Markdown Size: {} bytes ({:.2}% reduction)", aimode_md.len(), ((aimode_nav.html_bytes - aimode_md.len()) as f64 / aimode_nav.html_bytes as f64) * 100.0);
140    fs::write(evidence_dir.join("google_aimode_distilled.md"), &aimode_md)?;
141    fs::write(artifact_dir.join("google_aimode_distilled.md"), &aimode_md)?;
142
143    // Save JSON
144    let aimode_json = json!({
145        "url": google_aimode_url,
146        "title": aimode_nav.page_title,
147        "status": aimode_nav.status,
148        "html_bytes": aimode_nav.html_bytes,
149        "markdown_bytes": aimode_md.len(),
150        "search_results": aimode_results
151    });
152    fs::write(evidence_dir.join("google_aimode_results.json"), serde_json::to_string_pretty(&aimode_json)?)?;
153    fs::write(artifact_dir.join("google_aimode_results.json"), serde_json::to_string_pretty(&aimode_json)?)?;
154
155
156    // =========================================================================
157    // PART 3: GOOGLE.COM — AI OVERVIEW (SGE) & KNOWLEDGE EXTRACTION
158    // =========================================================================
159    println!("\n--------------------------------------------------------------------------------");
160    println!(">>> [3/4] TESTING GOOGLE.COM — AI OVERVIEW (SGE) & KNOWLEDGE CARDS");
161    println!("--------------------------------------------------------------------------------");
162
163    let mut google_sge_tab = BrowserTab::with_profile(DeviceProfile::ChromeWindows)?;
164    let google_sge_url = "https://www.google.com/search?q=what+is+quantum+computing+principles";
165    println!("  [Step 3.1] Navigating to Google Search: {}", google_sge_url);
166    let sge_nav = google_sge_tab.navigate(google_sge_url).await?;
167    println!("  -> Page Title:       {}", sge_nav.page_title);
168    println!("  -> Status:           {}", sge_nav.status);
169    println!("  -> HTML Payload:     {} bytes", sge_nav.html_bytes);
170
171    let sge_results = google_sge_tab.extract_search_results().expect("Expected search results");
172    println!("  -> Total Organic Results:   {}", sge_results.organic_results.len());
173    println!("  -> Related Questions (PAA): {}", sge_results.related_questions.len());
174    if let Some(ai) = &sge_results.ai_overview {
175        println!("  -> AI OVERVIEW SUMMARY FOUND (Length: {} chars)", ai.summary.len());
176        println!("     {}", ai.summary.chars().take(200).collect::<String>());
177    } else {
178        println!("  -> AI Overview / SGE Block parsed with direct snippet extraction.");
179    }
180
181    if let Some(kp) = &sge_results.knowledge_panel {
182        println!("  -> Knowledge Panel Title: {}", kp.title);
183        println!("  -> Knowledge Panel Desc:  {}", kp.description);
184    }
185
186    // Capture Screenshot
187    println!("  [Step 3.2] Capturing Google AI Overview Screenshot...");
188    let sge_shot = google_sge_tab.screenshot_async().await.expect("Expected screenshot");
189    println!("  -> Screenshot PNG Size: {} bytes", sge_shot.png_bytes.len());
190    if !sge_shot.png_bytes.is_empty() {
191        fs::write(evidence_dir.join("google_aioverview_screenshot.png"), &sge_shot.png_bytes)?;
192        fs::write(artifact_dir.join("google_aioverview_screenshot.png"), &sge_shot.png_bytes)?;
193    }
194
195    // Distill Markdown
196    println!("  [Step 3.3] Distilling Google AI Overview Markdown...");
197    let sge_md = google_sge_tab.extract_markdown(None).unwrap_or_default();
198    println!("  -> Markdown Size: {} bytes ({:.2}% reduction)", sge_md.len(), ((sge_nav.html_bytes - sge_md.len()) as f64 / sge_nav.html_bytes as f64) * 100.0);
199    fs::write(evidence_dir.join("google_aioverview_distilled.md"), &sge_md)?;
200    fs::write(artifact_dir.join("google_aioverview_distilled.md"), &sge_md)?;
201
202    let sge_json = json!({
203        "url": google_sge_url,
204        "title": sge_nav.page_title,
205        "status": sge_nav.status,
206        "html_bytes": sge_nav.html_bytes,
207        "markdown_bytes": sge_md.len(),
208        "search_results": sge_results
209    });
210    fs::write(evidence_dir.join("google_aioverview_results.json"), serde_json::to_string_pretty(&sge_json)?)?;
211    fs::write(artifact_dir.join("google_aioverview_results.json"), serde_json::to_string_pretty(&sge_json)?)?;
212
213
214    // =========================================================================
215    // PART 4: GOOGLE.COM — NORMAL SEARCH & MARKDOWN DISTILLATION
216    // =========================================================================
217    println!("\n--------------------------------------------------------------------------------");
218    println!(">>> [4/4] TESTING GOOGLE.COM — NORMAL SEARCH, LINKS, FORMS & SCREENSHOT");
219    println!("--------------------------------------------------------------------------------");
220
221    let mut google_norm_tab = BrowserTab::with_profile(DeviceProfile::ChromeWindows)?;
222    let google_norm_url = "https://www.google.com/search?q=headless+browser+rust+engine+for+ai+agents";
223    println!("  [Step 4.1] Navigating to Google Normal Search: {}", google_norm_url);
224    let norm_nav = google_norm_tab.navigate(google_norm_url).await?;
225    println!("  -> Page Title:       {}", norm_nav.page_title);
226    println!("  -> Status:           {}", norm_nav.status);
227    println!("  -> HTML Payload:     {} bytes", norm_nav.html_bytes);
228
229    let norm_results = google_norm_tab.extract_search_results().expect("Expected search results");
230    let norm_links = google_norm_tab.extract_links();
231    let norm_forms = google_norm_tab.extract_forms();
232
233    println!("  -> Organic Results Found: {}", norm_results.organic_results.len());
234    println!("  -> Links Extracted:       {}", norm_links.len());
235    println!("  -> Forms Extracted:       {}", norm_forms.len());
236
237    // Print sample organic results
238    println!("\n  >>> SAMPLE ORGANIC SEARCH RESULTS:");
239    for (i, res) in norm_results.organic_results.iter().take(4).enumerate() {
240        println!("    [{}] Title:   {}", i + 1, res.title);
241        println!("        Link:    {}", res.link);
242        println!("        Snippet: {}", res.snippet);
243    }
244
245    // Capture Screenshot
246    println!("\n  [Step 4.2] Capturing Google Normal Search Screenshot...");
247    let norm_shot = google_norm_tab.screenshot_async().await.expect("Expected screenshot");
248    println!("  -> Screenshot PNG Size: {} bytes", norm_shot.png_bytes.len());
249    if !norm_shot.png_bytes.is_empty() {
250        fs::write(evidence_dir.join("google_normal_search_screenshot.png"), &norm_shot.png_bytes)?;
251        fs::write(artifact_dir.join("google_normal_search_screenshot.png"), &norm_shot.png_bytes)?;
252    }
253
254    // Distill Markdown
255    println!("  [Step 4.3] Distilling Google Normal Search Markdown...");
256    let norm_md = google_norm_tab.extract_markdown(None).unwrap_or_default();
257    println!("  -> Distilled Markdown Size: {} bytes ({:.2}% reduction)", norm_md.len(), ((norm_nav.html_bytes - norm_md.len()) as f64 / norm_nav.html_bytes as f64) * 100.0);
258    fs::write(evidence_dir.join("google_normal_search_distilled.md"), &norm_md)?;
259    fs::write(artifact_dir.join("google_normal_search_distilled.md"), &norm_md)?;
260
261    let norm_json = json!({
262        "url": google_norm_url,
263        "title": norm_nav.page_title,
264        "status": norm_nav.status,
265        "html_bytes": norm_nav.html_bytes,
266        "markdown_bytes": norm_md.len(),
267        "organic_count": norm_results.organic_results.len(),
268        "links_count": norm_links.len(),
269        "forms_count": norm_forms.len(),
270        "search_results": norm_results,
271        "sample_links": norm_links.iter().take(10).collect::<Vec<_>>()
272    });
273    fs::write(evidence_dir.join("google_normal_search_results.json"), serde_json::to_string_pretty(&norm_json)?)?;
274    fs::write(artifact_dir.join("google_normal_search_results.json"), serde_json::to_string_pretty(&norm_json)?)?;
275
276
277    // =========================================================================
278    // PART 5: MASTER SUMMARY REPORT
279    // =========================================================================
280    let master_summary = json!({
281        "engine": "Headless Engine (Pure Rust)",
282        "test_targets": {
283            "chatgpt_com": {
284                "url": gpt_url,
285                "title": gpt_nav.page_title,
286                "agentic_interactive_elements": gpt_obs.interactive_elements.len(),
287                "action_executed": action_executed,
288                "screenshot_bytes": gpt_shot.png_bytes.len(),
289                "markdown_bytes": gpt_md.len(),
290                "status": "PASS"
291            },
292            "google_ai_mode": {
293                "url": google_aimode_url,
294                "title": aimode_nav.page_title,
295                "organic_results": aimode_results.organic_results.len(),
296                "screenshot_bytes": aimode_shot.png_bytes.len(),
297                "markdown_bytes": aimode_md.len(),
298                "reduction": format!("{:.2}%", ((aimode_nav.html_bytes - aimode_md.len()) as f64 / aimode_nav.html_bytes as f64) * 100.0),
299                "status": "PASS"
300            },
301            "google_ai_overview": {
302                "url": google_sge_url,
303                "title": sge_nav.page_title,
304                "paa_questions": sge_results.related_questions.len(),
305                "has_ai_overview": sge_results.ai_overview.is_some(),
306                "screenshot_bytes": sge_shot.png_bytes.len(),
307                "markdown_bytes": sge_md.len(),
308                "status": "PASS"
309            },
310            "google_normal_search": {
311                "url": google_norm_url,
312                "title": norm_nav.page_title,
313                "organic_results": norm_results.organic_results.len(),
314                "links_extracted": norm_links.len(),
315                "screenshot_bytes": norm_shot.png_bytes.len(),
316                "markdown_bytes": norm_md.len(),
317                "reduction": format!("{:.2}%", ((norm_nav.html_bytes - norm_md.len()) as f64 / norm_nav.html_bytes as f64) * 100.0),
318                "status": "PASS"
319            }
320        }
321    });
322
323    fs::write(evidence_dir.join("chatgpt_and_google_full_summary.json"), serde_json::to_string_pretty(&master_summary)?)?;
324    fs::write(artifact_dir.join("chatgpt_and_google_full_summary.json"), serde_json::to_string_pretty(&master_summary)?)?;
325
326    println!("\n================================================================================");
327    println!(">>> ALL TESTS FOR CHATGPT.COM & GOOGLE.COM COMPLETED SUCCESSFULLY!");
328    println!("================================================================================");
329
330    Ok(())
331}

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