Search code examples
testingrustchromiumchrome-devtools-protocol

Listen for console message with chromiumoxide


For the purpose of creating a test suite, I am using chromiumoxide and trying to execute some Javascript which contains console.assert statements and to listen for errors and any error messages. For example, the below executes a console.assert statement which throws an error. I would like to collect that error and it's message. In the docs I was able to find some types that seemed relevant to this task, eg CdpEvent, but I wasn't able to get any further than that.

use chromiumoxide::browser::{Browser, BrowserConfig};
use futures::StreamExt;
use tokio;

#[tokio::test]
async fn check_javascript() -> Result<(), Box<dyn std::error::Error>> {
    let (mut browser, mut handler) = Browser::launch(BrowserConfig::builder().build()?).await?;
    let handle = tokio::task::spawn(async move {
        while let Some(h) = handler.next().await {
            match h {
                Ok(_) => continue,
                Err(_) => break,
            }
        }
    });
    let page = browser.new_page("about:blank").await?;

    page.evaluate_expression("console.assert(2 === 3, { msg: 'numbers do not match' });").await?;
    
    browser.close().await?;
    handle.await?;
    Ok(())
}

Solution

  • We need to enable breakpoints on all exceptions (that includes console.asserts), and attach an event listener for "paused in debugger" events. I was unable to find how to extract the message, though.

    use std::error::Error;
    use std::sync::Arc;
    
    use chromiumoxide::browser::{Browser, BrowserConfig};
    use chromiumoxide::cdp::js_protocol::debugger::*;
    use futures::StreamExt;
    
    #[tokio::main]
    async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
        let (mut browser, mut handler) = Browser::launch(BrowserConfig::builder().build()?).await?;
        let handle = tokio::task::spawn(async move {
            while let Some(h) = handler.next().await {
                match h {
                    Ok(_) => continue,
                    Err(_) => break,
                }
            }
        });
        let page = browser.new_page("about:blank").await?;
        page.enable_debugger().await?;
    
        let mut events = page.event_listener::<EventPaused>().await?;
        let events_handle = tokio::spawn({
            let page = page.clone();
            async move {
                while let Some(event) = events.next().await {
                    if event.reason == PausedReason::Assert {
                        dbg!(event);
                    }
    
                    page.execute(ResumeParams::default()).await?;
                }
    
                Ok::<(), Box<dyn Error + Send + Sync>>(())
            }
        });
    
        page.execute(SetPauseOnExceptionsParams {
            state: SetPauseOnExceptionsState::All,
        })
        .await?;
    
        page.evaluate_expression("console.assert(2 === 3, { msg: 'numbers do not match' });")
            .await?;
    
        browser.close().await?;
        handle.await?;
        events_handle.await??;
        Ok(())
    }