use super::{StepCategory, StepDef, q};
pub fn register(steps: &mut Vec<Box<dyn StepDef>>) {
steps.push(Box::new(PageNotHasText));
steps.push(Box::new(PageHasText));
steps.push(Box::new(UrlContains));
steps.push(Box::new(UrlExact));
steps.push(Box::new(TitleContains));
steps.push(Box::new(TitleExact));
steps.push(Box::new(NotVisible));
steps.push(Box::new(Visible));
steps.push(Box::new(NotContainsText));
steps.push(Box::new(ContainsText));
steps.push(Box::new(TextExact));
steps.push(Box::new(ValueExact));
steps.push(Box::new(HasAttrValue));
steps.push(Box::new(NotHasAttr));
steps.push(Box::new(HasAttr));
steps.push(Box::new(NotHasClass));
steps.push(Box::new(HasClass));
steps.push(Box::new(Disabled));
steps.push(Box::new(Enabled));
steps.push(Box::new(NotChecked));
steps.push(Box::new(Checked));
steps.push(Box::new(ElementCount));
}
step!(PageHasText {
category: StepCategory::Assertion,
pattern: r"^the page should (?:have|contain) text (.+)$",
description: "Assert page body contains text",
example: "Then the page should contain text \"Welcome\"",
execute(page, caps, _table, _vars) {
let text = q(&caps[1]);
let loc = page.locator("body", None);
let content = loc.text_content().await?.unwrap_or_default();
if !content.contains(&text) {
return Err(crate::error::FerriError::backend(format!("Page does not contain text '{text}'")));
}
Ok(None)
}
});
step!(PageNotHasText {
category: StepCategory::Assertion,
pattern: r"^the page should not (?:have|contain) text (.+)$",
description: "Assert page body does not contain text",
example: "Then the page should not contain text \"Error\"",
execute(page, caps, _table, _vars) {
let text = q(&caps[1]);
let loc = page.locator("body", None);
let content = loc.text_content().await?.unwrap_or_default();
if content.contains(&text) {
return Err(crate::error::FerriError::backend(format!("Page contains text '{text}' but should not")));
}
Ok(None)
}
});
step!(UrlContains {
category: StepCategory::Assertion,
pattern: r"^the URL should contain (.+)$",
description: "Assert URL contains substring",
example: "Then the URL should contain \"/dashboard\"",
execute(page, caps, _table, _vars) {
let expected = q(&caps[1]);
let url = page.url();
if !url.contains(&expected) {
return Err(crate::error::FerriError::backend(format!("URL '{url}' does not contain '{expected}'")));
}
Ok(None)
}
});
step!(UrlExact {
category: StepCategory::Assertion,
pattern: r"^the URL should be (.+)$",
description: "Assert exact URL",
example: "Then the URL should be \"https://example.com/\"",
execute(page, caps, _table, _vars) {
let expected = q(&caps[1]);
let url = page.url();
if url != expected {
return Err(crate::error::FerriError::backend(format!("URL is '{url}', expected '{expected}'")));
}
Ok(None)
}
});
step!(TitleContains {
category: StepCategory::Assertion,
pattern: r"^the title should contain (.+)$",
description: "Assert title contains substring",
example: "Then the title should contain \"Dashboard\"",
execute(page, caps, _table, _vars) {
let expected = q(&caps[1]);
let title = page.title().await.unwrap_or_default();
if !title.contains(&expected) {
return Err(crate::error::FerriError::backend(format!("Title '{title}' does not contain '{expected}'")));
}
Ok(None)
}
});
step!(TitleExact {
category: StepCategory::Assertion,
pattern: r"^the title should be (.+)$",
description: "Assert exact title",
example: "Then the title should be \"My App\"",
execute(page, caps, _table, _vars) {
let expected = q(&caps[1]);
let title = page.title().await.unwrap_or_default();
if title != expected {
return Err(crate::error::FerriError::backend(format!("Title is '{title}', expected '{expected}'")));
}
Ok(None)
}
});
step!(Visible {
category: StepCategory::Assertion,
pattern: r"^(.+) should be visible$",
description: "Assert element exists and is visible",
example: "Then \"#dialog\" should be visible",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let loc = page.locator(&sel, None);
let visible = loc.is_visible().await.unwrap_or(false);
if !visible {
return Err(crate::error::FerriError::backend(format!("'{sel}' is not visible")));
}
Ok(None)
}
});
step!(NotVisible {
category: StepCategory::Assertion,
pattern: r"^(.+) should not be visible$",
description: "Assert element does not exist or is hidden",
example: "Then \"#spinner\" should not be visible",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let loc = page.locator(&sel, None);
let hidden = loc.is_hidden().await.unwrap_or(true);
if !hidden {
return Err(crate::error::FerriError::backend(format!("'{sel}' is visible but should not be")));
}
Ok(None)
}
});
step!(ContainsText {
category: StepCategory::Assertion,
pattern: r"^(.+) should contain text (.+)$",
description: "Assert element contains text",
example: "Then \"#message\" should contain text \"Success\"",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let expected = q(&caps[2]);
let loc = page.locator(&sel, None);
let text = loc.inner_text().await.map_err(|_| format!("'{sel}' not found"))?;
if !text.contains(&expected) {
return Err(crate::error::FerriError::backend(format!("'{sel}' text is '{text}', does not contain '{expected}'")));
}
Ok(None)
}
});
step!(NotContainsText {
category: StepCategory::Assertion,
pattern: r"^(.+) should not contain text (.+)$",
description: "Assert element does not contain text",
example: "Then \"#status\" should not contain text \"Error\"",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let expected = q(&caps[2]);
let loc = page.locator(&sel, None);
let text = loc.inner_text().await.map_err(|_| format!("'{sel}' not found"))?;
if text.contains(&expected) {
return Err(crate::error::FerriError::backend(format!("'{sel}' text '{text}' contains '{expected}' but should not")));
}
Ok(None)
}
});
step!(TextExact {
category: StepCategory::Assertion,
pattern: r"^(.+) should have text (.+)$",
description: "Assert element has exact text",
example: "Then \"h1\" should have text \"Welcome\"",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let expected = q(&caps[2]);
let loc = page.locator(&sel, None);
let text = loc.inner_text().await.map_err(|_| format!("'{sel}' not found"))?;
if text.trim() != expected.trim() {
return Err(crate::error::FerriError::backend(format!("'{sel}' text is '{text}', expected '{expected}'")));
}
Ok(None)
}
});
step!(ValueExact {
category: StepCategory::Assertion,
pattern: r"^(.+) should have value (.+)$",
description: "Assert input has value",
example: "Then \"#email\" should have value \"test@example.com\"",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let expected = q(&caps[2]);
let loc = page.locator(&sel, None);
let val = loc.input_value().await.map_err(|_| format!("'{sel}' not found"))?;
if val != expected {
return Err(crate::error::FerriError::backend(format!("'{sel}' value is '{val}', expected '{expected}'")));
}
Ok(None)
}
});
step!(HasAttrValue {
category: StepCategory::Assertion,
pattern: r"^(.+) should have attribute (.+) with value (.+)$",
description: "Assert attribute has value",
example: "Then \"#link\" should have attribute \"href\" with value \"/about\"",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let attr = q(&caps[2]);
let expected = q(&caps[3]);
let loc = page.locator(&sel, None);
let val = loc.get_attribute(&attr).await
.map_err(|_| format!("'{sel}' not found"))?
.unwrap_or_default();
if val != expected {
return Err(crate::error::FerriError::backend(format!("'{sel}' attribute '{attr}' is '{val}', expected '{expected}'")));
}
Ok(None)
}
});
step!(HasAttr {
category: StepCategory::Assertion,
pattern: r"^(.+) should have attribute (.+)$",
description: "Assert element has attribute",
example: "Then \"#input\" should have attribute \"required\"",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let attr = q(&caps[2]);
let loc = page.locator(&sel, None);
let val = loc.get_attribute(&attr).await
.map_err(|_| format!("'{sel}' not found"))?;
if val.is_none() {
return Err(crate::error::FerriError::backend(format!("'{sel}' does not have attribute '{attr}'")));
}
Ok(None)
}
});
step!(NotHasAttr {
category: StepCategory::Assertion,
pattern: r"^(.+) should not have attribute (.+)$",
description: "Assert element lacks attribute",
example: "Then \"#input\" should not have attribute \"disabled\"",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let attr = q(&caps[2]);
let loc = page.locator(&sel, None);
let val = loc.get_attribute(&attr).await
.map_err(|_| format!("'{sel}' not found"))?;
if val.is_some() {
return Err(crate::error::FerriError::backend(format!("'{sel}' has attribute '{attr}' but should not")));
}
Ok(None)
}
});
step!(HasClass {
category: StepCategory::Assertion,
pattern: r"^(.+) should have class (.+)$",
description: "Assert element has CSS class",
example: "Then \"#btn\" should have class \"active\"",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let cls = q(&caps[2]);
let loc = page.locator(&sel, None);
let classes = loc.get_attribute("class").await
.map_err(|_| format!("'{sel}' not found"))?
.unwrap_or_default();
if !classes.split_whitespace().any(|c| c == cls) {
return Err(crate::error::FerriError::backend(format!("'{sel}' does not have class '{cls}'")));
}
Ok(None)
}
});
step!(NotHasClass {
category: StepCategory::Assertion,
pattern: r"^(.+) should not have class (.+)$",
description: "Assert element lacks CSS class",
example: "Then \"#btn\" should not have class \"loading\"",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let cls = q(&caps[2]);
let loc = page.locator(&sel, None);
let classes = loc.get_attribute("class").await
.map_err(|_| format!("'{sel}' not found"))?
.unwrap_or_default();
if classes.split_whitespace().any(|c| c == cls) {
return Err(crate::error::FerriError::backend(format!("'{sel}' has class '{cls}' but should not")));
}
Ok(None)
}
});
step!(Enabled {
category: StepCategory::Assertion,
pattern: r"^(.+) should be enabled$",
description: "Assert element is enabled",
example: "Then \"#submit\" should be enabled",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let loc = page.locator(&sel, None);
let enabled = loc.is_enabled().await.map_err(|_| format!("'{sel}' not found"))?;
if !enabled {
return Err(crate::error::FerriError::backend(format!("'{sel}' is disabled")));
}
Ok(None)
}
});
step!(Disabled {
category: StepCategory::Assertion,
pattern: r"^(.+) should be disabled$",
description: "Assert element is disabled",
example: "Then \"#submit\" should be disabled",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let loc = page.locator(&sel, None);
let disabled = loc.is_disabled().await.map_err(|_| format!("'{sel}' not found"))?;
if !disabled {
return Err(crate::error::FerriError::backend(format!("'{sel}' is not disabled")));
}
Ok(None)
}
});
step!(Checked {
category: StepCategory::Assertion,
pattern: r"^(.+) should be checked$",
description: "Assert checkbox is checked",
example: "Then \"#agree\" should be checked",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let loc = page.locator(&sel, None);
let checked = loc.is_checked().await.map_err(|_| format!("'{sel}' not found"))?;
if !checked {
return Err(crate::error::FerriError::backend(format!("'{sel}' is not checked")));
}
Ok(None)
}
});
step!(NotChecked {
category: StepCategory::Assertion,
pattern: r"^(.+) should not be checked$",
description: "Assert checkbox is not checked",
example: "Then \"#agree\" should not be checked",
execute(page, caps, _table, _vars) {
let sel = q(&caps[1]);
let loc = page.locator(&sel, None);
let checked = loc.is_checked().await.map_err(|_| format!("'{sel}' not found"))?;
if checked {
return Err(crate::error::FerriError::backend(format!("'{sel}' is checked but should not be")));
}
Ok(None)
}
});
step!(ElementCount {
category: StepCategory::Assertion,
pattern: r"^there should be (\d+) (.+)$",
description: "Assert element count",
example: "Then there should be 3 \".item\"",
execute(page, caps, _table, _vars) {
let expected: usize = caps[1].parse().map_err(|_| "Invalid count")?;
let sel = q(&caps[2]);
let loc = page.locator(&sel, None);
let actual = loc.count().await?;
if actual != expected {
return Err(crate::error::FerriError::backend(format!("Found {actual} '{sel}', expected {expected}")));
}
Ok(None)
}
});