browser_tester 1.5.0

Deterministic lightweight browser runtime for Rust tests
Documentation
use super::*;

#[test]
fn element_set_html_unsafe_without_sanitizer_keeps_script_and_event_attrs() -> Result<()> {
    let html = r#"
        <div id='target'>old</div>
        <button id='run'>run</button>
        <p id='result'></p>
        <script>
          document.getElementById('run').addEventListener('click', () => {
            const target = document.getElementById('target');
            const returned = target.setHTMLUnsafe(
              "<p id='p' onclick='alert(1)'>P</p><script id='s'>window.__x = 1</" + "script>"
            );
            const p = target.querySelector('#p');
            const s = target.querySelector('#s');
            document.getElementById('result').textContent = [
              returned === undefined,
              p !== null,
              p !== null ? p.getAttribute('onclick') : 'none',
              s !== null
            ].join(':');
          });
        </script>
        "#;

    let mut h = Harness::from_html(html)?;
    h.click("#run")?;
    h.assert_text("#result", "true:true:alert(1):true")?;
    Ok(())
}

#[test]
fn element_set_html_unsafe_with_default_sanitizer_strips_xss_unsafe_markup() -> Result<()> {
    let html = r#"
        <div id='target'>old</div>
        <button id='run'>run</button>
        <p id='result'></p>
        <script>
          document.getElementById('run').addEventListener('click', () => {
            const target = document.getElementById('target');
            const returned = target.setHTMLUnsafe(
              "<p id='p' onclick='alert(1)'>P</p><script id='s'>window.__x = 1</" + "script>",
              { sanitizer: "default" }
            );
            const p = target.querySelector('#p');
            document.getElementById('result').textContent = [
              returned === undefined,
              p !== null,
              p !== null ? p.getAttribute('onclick') === null : false,
              target.querySelector('#s') === null
            ].join(':');
          });
        </script>
        "#;

    let mut h = Harness::from_html(html)?;
    h.click("#run")?;
    h.assert_text("#result", "true:true:true:true")?;
    Ok(())
}

#[test]
fn element_set_html_unsafe_with_config_remove_elements_filters_matching_tags() -> Result<()> {
    let html = r#"
        <div id='target'>old</div>
        <button id='run'>run</button>
        <p id='result'></p>
        <script>
          document.getElementById('run').addEventListener('click', () => {
            const target = document.getElementById('target');
            const returned = target.setHTMLUnsafe(
              "<p id='p'>P</p><script id='s'>X</" + "script><button id='b'>B</button>",
              { sanitizer: { removeElements: ["script", "button"] } }
            );
            document.getElementById('result').textContent = [
              returned === undefined,
              target.querySelector('#p') !== null,
              target.querySelector('#s') === null,
              target.querySelector('#b') === null
            ].join(':');
          });
        </script>
        "#;

    let mut h = Harness::from_html(html)?;
    h.click("#run")?;
    h.assert_text("#result", "true:true:true:true")?;
    Ok(())
}

#[test]
fn element_set_html_unsafe_creates_declarative_shadow_root() -> Result<()> {
    let html = r#"
        <div id='host'></div>
        <button id='run'>run</button>
        <p id='result'></p>
        <script>
          document.getElementById('run').addEventListener('click', () => {
            const host = document.getElementById('host');
            const returned = host.setHTMLUnsafe(
              "<template shadowrootmode='open'><span id='inside'>I</span></template><i id='outside'>O</i>"
            );
            const root = host.shadowRoot;
            document.getElementById('result').textContent = [
              returned === undefined,
              root !== null,
              root !== null ? root.querySelector('#inside').textContent : 'none',
              host.querySelector('template') === null,
              host.querySelector('#outside') !== null
            ].join(':');
          });
        </script>
        "#;

    let mut h = Harness::from_html(html)?;
    h.click("#run")?;
    h.assert_text("#result", "true:true:I:true:true")?;
    Ok(())
}

#[test]
fn element_set_html_unsafe_second_declarative_template_becomes_template_in_shadow_root()
-> Result<()> {
    let html = r#"
        <div id='host'></div>
        <button id='run'>run</button>
        <p id='result'></p>
        <script>
          document.getElementById('run').addEventListener('click', () => {
            const host = document.getElementById('host');
            host.setHTMLUnsafe(
              "<template shadowrootmode='open'><span id='first'>F</span></template>" +
              "<template shadowrootmode='closed'><span id='second'>S</span></template>"
            );
            const root = host.shadowRoot;
            const moved = root ? root.querySelector("template[shadowrootmode='closed']") : null;
            document.getElementById('result').textContent = [
              root !== null,
              root !== null ? root.querySelector('#first') !== null : false,
              moved !== null,
              host.querySelector('template') === null
            ].join(':');
          });
        </script>
        "#;

    let mut h = Harness::from_html(html)?;
    h.click("#run")?;
    h.assert_text("#result", "true:true:true:true")?;
    Ok(())
}

#[test]
fn set_html_unsafe_table_html_8_5_2_13_2_6_4_9_wraps_direct_rows_in_implied_tbody() -> Result<()> {
    let html = r#"
        <table id='scores'><caption>before</caption></table>
        <button id='run'>run</button>
        <p id='result'></p>
        <script>
          document.getElementById('run').addEventListener('click', () => {
            const table = document.getElementById('scores');
            table.setHTMLUnsafe('<tr id="a"><td>Alpha</td></tr><tr id="b"><td>Beta</td></tr>');
            document.getElementById('result').textContent = [
              document.querySelectorAll('#scores > tbody').length,
              document.querySelectorAll('#scores > tr').length,
              document.querySelectorAll('#scores > tbody > tr').length,
              Array.from(document.querySelectorAll('#scores > tbody > tr > td'))
                .map((cell) => cell.textContent)
                .join(',')
            ].join(':');
          });
        </script>
        "#;

    let mut h = Harness::from_html(html)?;
    h.click("#run")?;
    h.assert_text("#result", "1:0:2:Alpha,Beta")?;
    Ok(())
}

#[test]
fn element_set_html_unsafe_rejects_invalid_sanitizer_string_and_invalid_config() -> Result<()> {
    let html = r#"
        <div id='target'></div>
        <button id='run'>run</button>
        <p id='result'></p>
        <script>
          document.getElementById('run').addEventListener('click', () => {
            const target = document.getElementById('target');
            let badString = false;
            let badConfig = false;
            try {
              target.setHTMLUnsafe('<p>x</p>', { sanitizer: 'unsafe' });
            } catch (e) {
              badString = String(e).includes('TypeError');
            }
            try {
              target.setHTMLUnsafe('<p>x</p>', {
                sanitizer: { elements: ['p'], removeElements: ['script'] }
              });
            } catch (e) {
              badConfig = String(e).includes('TypeError');
            }
            document.getElementById('result').textContent = [badString, badConfig].join(':');
          });
        </script>
        "#;

    let mut h = Harness::from_html(html)?;
    h.click("#run")?;
    h.assert_text("#result", "true:true")?;
    Ok(())
}

#[test]
fn element_set_html_unsafe_rejects_wrong_argument_count() -> Result<()> {
    let html = r#"
        <div id='target'></div>
        <button id='run'>run</button>
        <script>
          document.getElementById('run').addEventListener('click', () => {
            document.getElementById('target').setHTMLUnsafe();
          });
        </script>
        "#;

    let mut h = Harness::from_html(html)?;
    match h.click("#run") {
        Err(Error::ScriptRuntime(message)) => {
            assert!(
                message.contains("setHTMLUnsafe requires one or two arguments"),
                "unexpected runtime error message: {message}"
            );
        }
        other => panic!("expected runtime error, got: {other:?}"),
    }
    Ok(())
}