use scraper::{Html, Selector};
pub fn assert_css_exists(html: &str, selector: &str) {
let document = Html::parse_document(html);
let parsed_selector = Selector::parse(selector).unwrap();
assert!(
document.select(&parsed_selector).count() > 0,
"Element matching selector '{selector:?}' not found"
);
}
pub fn assert_css_not_exists(html: &str, selector: &str) {
let document = Html::parse_document(html);
let parsed_selector = Selector::parse(selector).unwrap();
assert!(
document.select(&parsed_selector).count() == 0,
"Element matching selector '{selector:?}' should not exist"
);
}
pub fn assert_css_eq(html: &str, selector: &str, expected_text: &str) {
let document = Html::parse_document(html);
let parsed_selector = Selector::parse(selector).unwrap();
let mut found = false;
for element in document.select(&parsed_selector) {
let text = element.text().collect::<Vec<_>>().join("");
if text == expected_text {
found = true;
break;
}
}
assert!(
found,
"Text does not match: Expected '{expected_text:?}' but found a different value or no \
match for selector '{selector:?}'"
);
}
pub fn assert_link(html: &str, selector: &str, expected_href: &str) {
assert_attribute_eq(html, selector, "href", expected_href);
}
pub fn assert_attribute_exists(html: &str, selector: &str, attribute: &str) {
let document = Html::parse_document(html);
let parsed_selector = Selector::parse(selector).unwrap();
let mut found = false;
for element in document.select(&parsed_selector) {
if element.value().attr(attribute).is_some() {
found = true;
break;
}
}
assert!(
found,
"Element matching selector '{selector:?}' does not have the attribute '{attribute}'"
);
}
pub fn assert_attribute_eq(html: &str, selector: &str, attribute: &str, expected_value: &str) {
let document = Html::parse_document(html);
let parsed_selector = Selector::parse(selector).unwrap();
let mut found = false;
for element in document.select(&parsed_selector) {
if let Some(attr_value) = element.value().attr(attribute) {
if attr_value == expected_value {
found = true;
break;
}
}
}
assert!(
found,
"Expected attribute '{attribute}' with value '{expected_value}' for selector \
'{selector:?}', but found a different value or no value."
);
}
pub fn assert_count(html: &str, selector: &str, expected_count: usize) {
let document = Html::parse_document(html);
let parsed_selector = Selector::parse(selector).unwrap();
let count = document.select(&parsed_selector).count();
assert!(
count == expected_count,
"Expected {expected_count} elements matching selector '{selector:?}', but found {count} \
elements."
);
}
pub fn assert_css_eq_list(html: &str, selector: &str, expected_texts: &[&str]) {
let document = Html::parse_document(html);
let parsed_selector = Selector::parse(selector).unwrap();
let collected_texts: Vec<String> = document
.select(&parsed_selector)
.map(|element| element.text().collect::<Vec<_>>().concat())
.collect();
assert_eq!(
collected_texts, expected_texts,
"Expected texts {expected_texts:?}, but found {collected_texts:?}."
);
}
#[must_use]
pub fn select(html: &str, selector: &str) -> Vec<String> {
let document = Html::parse_document(html);
let parsed_selector = Selector::parse(selector).unwrap();
document
.select(&parsed_selector)
.map(|element| element.html())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn setup_test_html() -> &'static str {
r#"
<html>
<body>
<div class="some-class">Some content here</div>
<div class="another-class">Another content here</div>
<h1 class="title">Welcome to Loco</h1>
<button onclick="alert('clicked')">Loco Website</button>
<a href="https://loco.rs">Link</a>
<ul id="posts">
<li>Post 1</li>
<li>Post 2</li>
<li>Post 3</li>
</ul>
<body>
<table id="posts_table">
<tr>
<td>Post 1</td>
<td>Author 1</td>
</tr>
<tr>
<td>Post 2</td>
<td>Author 2</td>
</tr>
<tr>
<td>Post 3</td>
<td>Author 3</td>
</tr>
</table>
</body>
</body>
</html>
"#
}
#[test]
fn test_assert_css_exists() {
let html = setup_test_html();
assert_css_exists(html, ".some-class");
let result = std::panic::catch_unwind(|| {
assert_css_exists(html, ".nonexistent-class");
});
assert!(result.is_err(), "Expected panic for non-existent selector");
if let Err(panic_message) = result {
let panic_message = panic_message.downcast_ref::<String>().unwrap();
assert_eq!(
panic_message,
&"Element matching selector '\".nonexistent-class\"' not found"
);
}
}
#[test]
fn test_assert_css_not_exists() {
let html = setup_test_html();
assert_css_not_exists(html, ".nonexistent-class");
let result = std::panic::catch_unwind(|| {
assert_css_not_exists(html, ".some-class");
});
assert!(result.is_err(), "Expected panic for non-existent selector");
if let Err(panic_message) = result {
let panic_message = panic_message.downcast_ref::<String>().unwrap();
assert_eq!(
panic_message,
&"Element matching selector '\".some-class\"' should not exist"
);
}
}
#[test]
fn test_assert_css_eq() {
let html = setup_test_html();
assert_css_eq(html, "h1.title", "Welcome to Loco");
let result = std::panic::catch_unwind(|| {
assert_css_eq(html, "h1.title", "Wrong text");
});
assert!(result.is_err(), "Expected panic for mismatched text");
if let Err(panic_message) = result {
let panic_message = panic_message.downcast_ref::<String>().unwrap();
assert_eq!(
panic_message,
&"Text does not match: Expected '\"Wrong text\"' but found a different value or \
no match for selector '\"h1.title\"'"
);
}
}
#[test]
fn test_assert_link() {
let html = setup_test_html();
assert_link(html, "a", "https://loco.rs");
let result = std::panic::catch_unwind(|| {
assert_link(html, "a", "https://nonexistent.com");
});
assert!(result.is_err());
if let Err(panic_message) = result {
let panic_message = panic_message.downcast_ref::<String>().unwrap();
assert_eq!(
panic_message,
&"Expected attribute 'href' with value 'https://nonexistent.com' for selector \
'\"a\"', but found a different value or no value."
);
}
}
#[test]
fn test_assert_attribute_exists() {
let html = setup_test_html();
assert_attribute_exists(html, "button", "onclick");
assert_attribute_exists(html, "a", "href");
let result = std::panic::catch_unwind(|| {
assert_attribute_exists(html, "button", "href");
});
if let Err(panic_message) = result {
let panic_message = panic_message.downcast_ref::<String>().unwrap();
assert_eq!(
panic_message,
&"Element matching selector '\"button\"' does not have the attribute 'href'"
);
}
}
#[test]
fn test_assert_attribute_eq() {
let html = setup_test_html();
assert_attribute_eq(html, "button", "onclick", "alert('clicked')");
assert_attribute_eq(html, "a", "href", "https://loco.rs");
let result = std::panic::catch_unwind(|| {
assert_attribute_eq(html, "button", "onclick", "alert('wrong')");
});
assert!(result.is_err());
if let Err(panic_message) = result {
let panic_message = panic_message.downcast_ref::<String>().unwrap();
assert_eq!(
panic_message,
&"Expected attribute 'onclick' with value 'alert('wrong')' for selector \
'\"button\"', but found a different value or no value."
);
}
}
#[test]
fn test_assert_count() {
let html = setup_test_html();
assert_count(html, "ul#posts li", 3);
let result = std::panic::catch_unwind(|| {
assert_count(html, "ul#posts li", 1);
});
assert!(result.is_err());
if let Err(panic_message) = result {
let panic_message = panic_message.downcast_ref::<String>().unwrap();
assert_eq!(
panic_message,
&"Expected 1 elements matching selector '\"ul#posts li\"', but found 3 elements."
);
}
}
#[test]
fn test_assert_css_eq_list() {
let html = setup_test_html();
assert_css_eq_list(html, "ul#posts li", &["Post 1", "Post 2", "Post 3"]);
let result = std::panic::catch_unwind(|| {
assert_css_eq_list(html, "ul#posts li", &["Post 1", "Post 2", "Wrong Post"]);
});
assert!(result.is_err());
if let Err(panic_message) = result {
let panic_message = panic_message.downcast_ref::<String>().unwrap();
assert_eq!(
panic_message,
&"assertion `left == right` failed: Expected texts [\"Post 1\", \"Post 2\", \
\"Wrong Post\"], but found [\"Post 1\", \"Post 2\", \"Post 3\"].\n left: \
[\"Post 1\", \"Post 2\", \"Post 3\"]\n right: [\"Post 1\", \"Post 2\", \"Wrong \
Post\"]"
);
}
}
#[test]
fn test_assert_css_eq_list_table() {
let html = setup_test_html();
assert_css_eq_list(
html,
"table tr td",
&[
"Post 1", "Author 1", "Post 2", "Author 2", "Post 3", "Author 3",
],
);
let result = std::panic::catch_unwind(|| {
assert_css_eq_list(html, "table#posts_t tr td", &["Post 1", "Post 2", "Post 3"]);
});
assert!(result.is_err());
if let Err(panic_message) = result {
let panic_message = panic_message.downcast_ref::<String>().unwrap();
assert_eq!(
panic_message,
&"assertion `left == right` failed: Expected texts [\"Post 1\", \"Post 2\", \
\"Post 3\"], but found [].\n left: []\n right: [\"Post 1\", \"Post 2\", \"Post \
3\"]"
);
}
}
#[test]
fn test_select() {
let html = setup_test_html();
assert_eq!(
select(html, ".some-class"),
vec!["<div class=\"some-class\">Some content here</div>"]
);
assert_eq!(select(html, "ul"), vec!["<ul id=\"posts\">\n <li>Post 1</li>\n <li>Post 2</li>\n <li>Post 3</li>\n </ul>"]);
}
}