///|
pub struct Expect[T] {
actual : T
}
///|
pub fn[T] expect(actual : T) -> Expect[T] {
{ actual, }
}
///|
#callsite(autofill(loc))
pub fn[T : Eq + Show] Expect::to_be(
self : Expect[T],
expected : T,
msg? : String,
loc~ : SourceLoc,
) -> Unit raise {
assert_eq(self.actual, expected, msg?, loc~)
}
///|
#callsite(autofill(loc))
pub fn[T : Eq + Show] Expect::to_equal(
self : Expect[T],
expected : T,
msg? : String,
loc~ : SourceLoc,
) -> Unit raise {
assert_eq(self.actual, expected, msg?, loc~)
}
///|
#callsite(autofill(loc))
pub fn Expect::to_match(
self : Expect[String],
pattern : String,
msg? : String,
loc~ : SourceLoc,
) -> Unit raise {
let ok = regex_match(self.actual, pattern)
if ok {
return ()
}
let message = match msg {
Some(m) => m
None => "expected \"" + self.actual + "\" to match /" + pattern + "/"
}
assert_eq(ok, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub fn Expect::to_be_truthy(
self : Expect[Bool],
msg? : String,
loc~ : SourceLoc,
) -> Unit raise {
let message = match msg {
Some(m) => m
None => "expected value to be truthy"
}
assert_eq(self.actual, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub fn Expect::to_be_falsy(
self : Expect[Bool],
msg? : String,
loc~ : SourceLoc,
) -> Unit raise {
let message = match msg {
Some(m) => m
None => "expected value to be falsy"
}
assert_eq(self.actual, false, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub fn Expect::to_contain(
self : Expect[String],
expected : String,
msg? : String,
loc~ : SourceLoc,
) -> Unit raise {
let ok = self.actual.contains(expected)
if ok {
return ()
}
let message = match msg {
Some(m) => m
None => "expected \"" + self.actual + "\" to contain \"" + expected + "\""
}
assert_eq(ok, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub fn[T : Eq + Show] Expect::to_contain_item(
self : Expect[Array[T]],
expected : T,
msg? : String,
loc~ : SourceLoc,
) -> Unit raise {
let mut ok = false
for item in self.actual {
if item == expected {
ok = true
break
}
}
if ok {
return ()
}
let message = match msg {
Some(m) => m
None => "expected array to contain " + expected.to_string()
}
assert_eq(ok, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub fn Expect::to_have_length(
self : Expect[String],
expected : Int,
msg? : String,
loc~ : SourceLoc,
) -> Unit raise {
let actual = self.actual.length()
let message = match msg {
Some(m) => m
None =>
"expected length " +
expected.to_string() +
" but was " +
actual.to_string()
}
assert_eq(actual, expected, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub fn[T] Expect::to_have_length_items(
self : Expect[Array[T]],
expected : Int,
msg? : String,
loc~ : SourceLoc,
) -> Unit raise {
let actual = self.actual.length()
let message = match msg {
Some(m) => m
None =>
"expected length " +
expected.to_string() +
" but was " +
actual.to_string()
}
assert_eq(actual, expected, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub fn Expect::to_be_greater_than(
self : Expect[Int],
expected : Int,
msg? : String,
loc~ : SourceLoc,
) -> Unit raise {
let ok = self.actual > expected
if ok {
return ()
}
let message = match msg {
Some(m) => m
None =>
"expected " +
self.actual.to_string() +
" to be greater than " +
expected.to_string()
}
assert_eq(ok, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub fn Expect::to_be_less_than(
self : Expect[Int],
expected : Int,
msg? : String,
loc~ : SourceLoc,
) -> Unit raise {
let ok = self.actual < expected
if ok {
return ()
}
let message = match msg {
Some(m) => m
None =>
"expected " +
self.actual.to_string() +
" to be less than " +
expected.to_string()
}
assert_eq(ok, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub fn Expect::to_be_close_to(
self : Expect[Double],
expected : Double,
precision? : Int,
msg? : String,
loc~ : SourceLoc,
) -> Unit raise {
let digits = match precision {
Some(value) => if value < 0 { 0 } else { value }
None => 2
}
let mut tolerance = 1.0
for i = 0 {
if i >= digits {
break
}
tolerance = tolerance / 10.0
continue i + 1
}
let diff = if self.actual > expected {
self.actual - expected
} else {
expected - self.actual
}
let ok = diff <= tolerance / 2.0
if ok {
return ()
}
let message = match msg {
Some(m) => m
None =>
"expected " +
self.actual.to_string() +
" to be close to " +
expected.to_string()
}
assert_eq(ok, true, msg=message, loc~)
}
///|
priv enum TextMatchKind {
Equal
Contain
}
///|
fn normalize_text(text : String) -> String {
let chars = text.to_array()
let out : Array[Char] = []
let in_space : Ref[Bool] = { val: false }
for ch in chars {
if ch.is_whitespace() {
if out.length() == 0 {
in_space.val = true
continue
}
if not(in_space.val) {
out.push(' ')
in_space.val = true
}
continue
}
out.push(ch)
in_space.val = false
}
if out.length() > 0 && out[out.length() - 1] == ' ' {
let _ = out.pop()
}
String::from_array(out)
}
///|
fn normalize_optional_text(value : String?) -> String {
match value {
Some(text) => if text == "null" || text == "undefined" { "" } else { text }
None => ""
}
}
///|
fn is_digit(ch : Char) -> Bool {
ch >= '0' && ch <= '9'
}
///|
fn is_alpha(ch : Char) -> Bool {
(ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
}
///|
fn parse_length(text : String) -> (Double, String)? {
let chars = text.to_array()
let mut index = 0
while index < chars.length() && chars[index].is_whitespace() {
index = index + 1
}
let mut sign = 1.0
if index < chars.length() {
if chars[index] == '-' {
sign = -1.0
index = index + 1
} else if chars[index] == '+' {
index = index + 1
}
}
let mut int_part = 0
let mut has_digit = false
while index < chars.length() && is_digit(chars[index]) {
int_part = int_part * 10 + (chars[index].to_int() - '0'.to_int())
has_digit = true
index = index + 1
}
let mut frac_part = 0
let mut frac_div = 1.0
if index < chars.length() && chars[index] == '.' {
index = index + 1
while index < chars.length() && is_digit(chars[index]) {
frac_part = frac_part * 10 + (chars[index].to_int() - '0'.to_int())
frac_div = frac_div * 10.0
has_digit = true
index = index + 1
}
}
if not(has_digit) {
return None
}
let unit_sb = StringBuilder::new()
while index < chars.length() {
let ch = chars[index]
if is_alpha(ch) || ch == '%' {
unit_sb.write_char(ch)
index = index + 1
continue
}
if ch.is_whitespace() {
break
}
break
}
let value = (int_part.to_double() + frac_part.to_double() / frac_div) * sign
let unit = unit_sb.to_string().to_lower()
Some((value, unit))
}
///|
fn normalize_text_regex_pattern(pattern : String) -> String {
if pattern.length() >= 2 && pattern.has_prefix("/") && pattern.has_suffix("/") {
let view = pattern.sub(start=1, end=pattern.length() - 1) catch {
_ => return pattern
}
view.to_string()
} else {
pattern
}
}
///|
fn is_regex_pattern(text : String) -> Bool {
text.length() >= 2 && text.has_prefix("/") && text.has_suffix("/")
}
///|
fn class_tokens(text : String) -> Array[String] {
let norm = normalize_text(text)
if norm.length() == 0 {
return []
}
let out : Array[String] = []
let mut sb = StringBuilder::new()
let mut has_token = false
for ch in norm {
if ch == ' ' {
if has_token {
out.push(sb.to_string())
sb = StringBuilder::new()
has_token = false
}
} else {
sb.write_char(ch)
has_token = true
}
}
if has_token {
out.push(sb.to_string())
}
out
}
///|
fn normalize_class_tokens(tokens : Array[String]) -> Array[String] {
let out : Array[String] = []
for token in tokens {
let parts = class_tokens(token)
for part in parts {
out.push(part)
}
}
out
}
///|
fn class_tokens_contains_all(
actual_tokens : Array[String],
expected_tokens : Array[String],
) -> Bool {
let mut ok = true
for expected in expected_tokens {
let mut found = false
for actual in actual_tokens {
if actual == expected {
found = true
break
}
}
if not(found) {
ok = false
break
}
}
ok
}
///|
fn join_list(items : Array[String]) -> String {
let sb = StringBuilder::new()
let mut first = true
for item in items {
if not(first) {
sb.write_view(", ")
}
sb.write_view(item)
first = false
}
sb.to_string()
}
///|
fn resolve_text_match_mode(
kind : TextMatchKind,
match_mode : String?,
) -> String {
match match_mode {
Some(mode) => {
let lower = mode.to_lower()
if lower == "regex" || lower == "re" {
"regex"
} else if lower == "contains" ||
lower == "include" ||
lower == "substring" {
"contains"
} else {
"exact"
}
}
None =>
match kind {
TextMatchKind::Equal => "exact"
TextMatchKind::Contain => "contains"
}
}
}
///|
fn class_matches(
actual : String,
expected : String,
match_mode : String,
normalize : Bool,
) -> Bool {
let actual_text = if normalize { normalize_text(actual) } else { actual }
let expected_text = if normalize {
normalize_text(expected)
} else {
expected
}
match match_mode {
"regex" =>
regex_match(actual_text, normalize_text_regex_pattern(expected_text))
"contains" =>
if expected_text.contains(" ") {
actual_text.contains(expected_text)
} else {
let tokens = class_tokens(actual_text)
let mut ok = false
for token in tokens {
if token == expected_text {
ok = true
break
}
}
ok
}
_ => actual_text == expected_text
}
}
///|
fn class_expected_matches(
actual : String,
expected : String,
match_mode : String,
normalize : Bool,
expected_tokens : Array[String],
) -> Bool {
let use_token_mode = match_mode != "regex" &&
(match_mode == "tokens" || expected_tokens.length() > 0)
if use_token_mode {
let actual_text = if normalize { normalize_text(actual) } else { actual }
let actual_list = class_tokens(actual_text)
let expected_list = if expected_tokens.length() > 0 {
expected_tokens
} else {
class_tokens(if normalize { normalize_text(expected) } else { expected })
}
let ok_all = class_tokens_contains_all(actual_list, expected_list)
if match_mode == "exact" {
ok_all && actual_list.length() == expected_list.length()
} else {
ok_all
}
} else {
class_matches(actual, expected, match_mode, normalize)
}
}
///|
fn resolve_class_mode_for_expected(
expected : String,
base_mode : String,
tokens : Array[String],
) -> String {
if base_mode == "tokens" || tokens.length() > 0 {
"tokens"
} else if base_mode == "regex" {
"regex"
} else if is_regex_pattern(expected) {
"regex"
} else {
base_mode
}
}
///|
fn length_to_px(
value : Double,
unit : String,
root_font : Double,
em_size : Double,
percent_base : Double?,
viewport_width : Double?,
viewport_height : Double?,
) -> Double? {
match unit {
"" => Some(value)
"px" => Some(value)
"rem" => Some(value * root_font)
"em" => Some(value * em_size)
"vw" =>
match viewport_width {
Some(base) => Some(value * base / 100.0)
None => None
}
"vh" =>
match viewport_height {
Some(base) => Some(value * base / 100.0)
None => None
}
"%" =>
match percent_base {
Some(base) => Some(value * base / 100.0)
None => None
}
_ => None
}
}
///|
fn infer_percent_base(
name : String,
viewport_width : Double?,
viewport_height : Double?,
) -> Double? {
let lower = name.to_lower()
if lower.contains("width") ||
lower.contains("left") ||
lower.contains("right") {
viewport_width
} else if lower.contains("height") ||
lower.contains("top") ||
lower.contains("bottom") {
viewport_height
} else {
None
}
}
///|
fn numeric_matches(
actual : String,
expected : String,
tolerance : Double,
relative : Bool,
root_font : Double,
em_size : Double,
percent_base : Double?,
viewport_width : Double?,
viewport_height : Double?,
) -> Bool {
match (parse_length(expected), parse_length(actual)) {
(Some((exp_value, exp_unit)), Some((act_value, act_unit))) => {
let exp_px = length_to_px(
exp_value, exp_unit, root_font, em_size, percent_base, viewport_width, viewport_height,
)
let act_px = length_to_px(
act_value, act_unit, root_font, em_size, percent_base, viewport_width, viewport_height,
)
let (lhs, rhs) = match (exp_px, act_px) {
(Some(lhs), Some(rhs)) => (lhs, rhs)
_ =>
if exp_unit == act_unit {
(exp_value, act_value)
} else {
return false
}
}
let diff = if rhs > lhs { rhs - lhs } else { lhs - rhs }
if relative {
let base = if lhs < 0.0 { -lhs } else { lhs }
let rel_tol = if tolerance <= 0.0 { 0.01 } else { tolerance }
diff <= base * rel_tol
} else {
diff <= tolerance
}
}
_ => false
}
}
///|
fn text_matches(
actual : String,
expected : String,
match_mode : String,
normalize : Bool,
) -> Bool {
let actual_text = if normalize { normalize_text(actual) } else { actual }
let expected_text = if normalize {
normalize_text(expected)
} else {
expected
}
match match_mode {
"regex" =>
regex_match(actual_text, normalize_text_regex_pattern(expected_text))
"contains" => actual_text.contains(expected_text)
_ => actual_text == expected_text
}
}
///|
async fn[T] wait_for_match(
timeout_ms : Int,
poll : async () -> T,
matches : (T) -> Bool,
initial : T,
) -> (Bool, T) {
let start = @async.now()
let last : Ref[T] = { val: initial }
let matched = for attempt = 0 {
let value = poll()
last.val = value
if matches(value) {
break true
}
if timeout_ms <= 0 {
break false
}
let elapsed = @async.now() - start
if elapsed >= timeout_ms.to_int64() {
break false
}
let remaining = timeout_ms - elapsed.to_int()
let base = 10
let cap = 200
let step = if attempt > 6 { 6 } else { attempt }
let delay = base * (1 << step)
let wait = if delay > cap { cap } else { delay }
let wait_ms = if wait > remaining { remaining } else { wait }
sleep_ms(wait_ms)
continue attempt + 1
}
(matched, last.val)
}
///|
async fn wait_for_text_match(
locator : Locator,
expected : String,
kind : TextMatchKind,
timeout_ms : Int,
match_mode : String?,
normalize : Bool?,
) -> (Bool, String) {
let normalize_value = match normalize {
Some(value) => value
None => true
}
let mode = resolve_text_match_mode(kind, match_mode)
let (matched, last) = wait_for_match(
timeout_ms,
async fn() { locator.inner_text() },
fn(text : String) { text_matches(text, expected, mode, normalize_value) },
"",
)
(matched, last)
}
///|
fn locator_with_index(locator : Locator, index : Int) -> Locator {
let state = locator.state.val
let next : LocatorState = {
page: state.page,
selector: state.selector,
index: Some(index),
has_text: state.has_text,
has_text_exact: state.has_text_exact,
has_not_text: state.has_not_text,
label_text: state.label_text,
label_text_exact: state.label_text_exact,
label_scope_selector: state.label_scope_selector,
}
{ state: { val: next } }
}
///|
async fn locator_exists(locator : Locator) -> Bool {
let expr = locator.element_expression() + " != null"
match locator.eval_bool(expr) {
Some(value) => value
None => false
}
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_have_text(
self : Expect[Locator],
expected : String,
timeout? : Int,
msg? : String,
match_mode? : String,
normalize? : Bool,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let (matched, actual) = wait_for_text_match(
self.actual,
expected,
TextMatchKind::Equal,
timeout_ms,
match_mode,
normalize,
)
if matched {
return ()
}
let message = match msg {
Some(value) => value
None =>
"expected text to be \"" + expected + "\" but was \"" + actual + "\""
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_contain_text(
self : Expect[Locator],
expected : String,
timeout? : Int,
msg? : String,
match_mode? : String,
normalize? : Bool,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let (matched, actual) = wait_for_text_match(
self.actual,
expected,
TextMatchKind::Contain,
timeout_ms,
match_mode,
normalize,
)
if matched {
return ()
}
let message = match msg {
Some(value) => value
None =>
"expected text to contain \"" + expected + "\" but was \"" + actual + "\""
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_have_class(
self : Expect[Locator],
expected : String,
expected_tokens? : Array[String],
expected_list? : Array[String],
expected_list_tokens? : Array[Array[String]],
timeout? : Int,
msg? : String,
match_mode? : String,
normalize? : Bool,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let normalize_value = match normalize {
Some(value) => value
None => true
}
let token_list = match expected_tokens {
Some(tokens) => normalize_class_tokens(tokens)
None => []
}
let expected_list_value = match expected_list {
Some(list) => list
None => []
}
let expected_list_tokens_value = match expected_list_tokens {
Some(list) => list
None => []
}
let mode = match match_mode {
Some(value) => {
let lower = value.to_lower()
if lower == "tokens" ||
lower == "token" ||
lower == "classes" ||
lower == "classlist" {
"tokens"
} else {
resolve_text_match_mode(TextMatchKind::Equal, match_mode)
}
}
None => if token_list.length() > 0 { "tokens" } else { "exact" }
}
if expected_list_value.length() > 0 {
let initial : (Array[String], Bool, Bool) = ([], false, false)
let (matched, result) = wait_for_match(
timeout_ms,
async fn() {
let actuals : Array[String] = []
let mut all_present = true
let mut index = 0
while index < expected_list_value.length() {
let loc = locator_with_index(self.actual, index)
let exists = locator_exists(loc)
if not(exists) {
all_present = false
actuals.push("")
} else {
let value = loc.get_attribute("class")
actuals.push(normalize_optional_text(value))
}
index = index + 1
}
let extra_index = expected_list_value.length()
let extra_exists = if extra_index == 0 {
locator_exists(locator_with_index(self.actual, 0))
} else {
locator_exists(locator_with_index(self.actual, extra_index))
}
(actuals, all_present, extra_exists)
},
fn(result : (Array[String], Bool, Bool)) {
let (actuals, all_present, extra_exists) = result
if not(all_present) {
return false
}
if extra_exists {
return false
}
if actuals.length() != expected_list_value.length() {
return false
}
let mut ok = true
let mut index = 0
while index < expected_list_value.length() {
let actual_value = actuals[index]
let expected_value = expected_list_value[index]
let item_tokens = if expected_list_tokens_value.length() > 0 &&
index < expected_list_tokens_value.length() {
normalize_class_tokens(expected_list_tokens_value[index])
} else {
token_list
}
let item_mode = resolve_class_mode_for_expected(
expected_value, mode, item_tokens,
)
if not(
class_expected_matches(
actual_value, expected_value, item_mode, normalize_value, item_tokens,
),
) {
ok = false
break
}
index = index + 1
}
ok
},
initial,
)
if matched {
return ()
}
let (actuals, _, extra_exists) = result
let extra_note = if extra_exists { " (extra elements)" } else { "" }
let message = match msg {
Some(value) => value
None =>
"expected class list to match [" +
join_list(expected_list_value) +
"] but was [" +
join_list(actuals) +
"]" +
extra_note
}
assert_eq(matched, true, msg=message, loc~)
} else {
let (matched, actual) = wait_for_match(
timeout_ms,
async fn() {
let value = self.actual.get_attribute("class")
normalize_optional_text(value)
},
fn(value : String) {
class_expected_matches(
value, expected, mode, normalize_value, token_list,
)
},
"",
)
if matched {
return ()
}
let message = match msg {
Some(value) => value
None =>
"expected class to match \"" +
expected +
"\" but was \"" +
actual +
"\""
}
assert_eq(matched, true, msg=message, loc~)
}
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_have_css(
self : Expect[Locator],
name : String,
expected : String,
timeout? : Int,
msg? : String,
match_mode? : String,
normalize? : Bool,
numeric? : Bool,
tolerance? : Double,
relative? : Bool,
root_font_size? : Double,
em_size? : Double,
percent_base? : Double,
viewport_width? : Double,
viewport_height? : Double,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let normalize_value = match normalize {
Some(value) => value
None => true
}
let numeric_value = match numeric {
Some(true) => true
_ => false
}
let relative_value = match relative {
Some(true) => true
_ => false
}
let tolerance_value = match tolerance {
Some(value) => if value < 0.0 { 0.0 } else { value }
None => 0.0
}
let root_font_value = match root_font_size {
Some(value) => if value <= 0.0 { 16.0 } else { value }
None => 16.0
}
let em_value = match em_size {
Some(value) => if value <= 0.0 { root_font_value } else { value }
None => root_font_value
}
let viewport_width_value = match viewport_width {
Some(value) => if value <= 0.0 { None } else { Some(value) }
None => None
}
let viewport_height_value = match viewport_height {
Some(value) => if value <= 0.0 { None } else { Some(value) }
None => None
}
let percent_base_value = match percent_base {
Some(value) => if value <= 0.0 { None } else { Some(value) }
None =>
infer_percent_base(name, viewport_width_value, viewport_height_value)
}
let mode = resolve_text_match_mode(TextMatchKind::Equal, match_mode)
let expr = "(function(){ const el = " +
self.actual.element_expression() +
"; if (!el) return ''; const doc = el.ownerDocument || document; const view = doc.defaultView || window; if (!view || !view.getComputedStyle) return ''; const style = view.getComputedStyle(el); if (!style) return ''; const value = style.getPropertyValue(" +
js_string_literal(name) +
"); return value == null ? '' : String(value); })()"
let (matched, actual) = wait_for_match(
timeout_ms,
async fn() {
let value = self.actual.eval_string(expr)
normalize_optional_text(value)
},
fn(value : String) {
if numeric_value {
let value_text = if normalize_value {
normalize_text(value)
} else {
value
}
numeric_matches(
value_text, expected, tolerance_value, relative_value, root_font_value,
em_value, percent_base_value, viewport_width_value, viewport_height_value,
)
} else {
text_matches(value, expected, mode, normalize_value)
}
},
"",
)
if matched {
return ()
}
let message = match msg {
Some(value) => value
None =>
"expected css \"" +
name +
"\" to match \"" +
expected +
"\" but was \"" +
actual +
"\""
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_be_focused(
self : Expect[Locator],
timeout? : Int,
msg? : String,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let expr = "(function(){ const el = " +
self.actual.element_expression() +
"; if (!el) return false; const doc = el.ownerDocument || document; const active = doc.activeElement; return el === active; })()"
let (matched, actual) = wait_for_match(
timeout_ms,
async fn() {
match self.actual.eval_bool(expr) {
Some(value) => value
None => false
}
},
fn(value : Bool) { value },
false,
)
if matched {
return ()
}
let state = if actual { "focused" } else { "not focused" }
let message = match msg {
Some(value) => value
None => "expected locator to be focused but was " + state
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_be_visible(
self : Expect[Locator],
timeout? : Int,
msg? : String,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let (matched, actual) = wait_for_match(
timeout_ms,
async fn() { self.actual.is_visible() },
fn(value : Bool) { value },
false,
)
if matched {
return ()
}
let state = if actual { "visible" } else { "hidden" }
let message = match msg {
Some(value) => value
None => "expected locator to be visible but was " + state
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_be_hidden(
self : Expect[Locator],
timeout? : Int,
msg? : String,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let (matched, actual) = wait_for_match(
timeout_ms,
async fn() { self.actual.is_hidden() },
fn(value : Bool) { value },
false,
)
if matched {
return ()
}
let state = if actual { "hidden" } else { "visible" }
let message = match msg {
Some(value) => value
None => "expected locator to be hidden but was " + state
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_be_enabled(
self : Expect[Locator],
timeout? : Int,
msg? : String,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let (matched, actual) = wait_for_match(
timeout_ms,
async fn() { self.actual.is_enabled() },
fn(value : Bool) { value },
false,
)
if matched {
return ()
}
let state = if actual { "enabled" } else { "disabled" }
let message = match msg {
Some(value) => value
None => "expected locator to be enabled but was " + state
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_be_disabled(
self : Expect[Locator],
timeout? : Int,
msg? : String,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let (matched, actual) = wait_for_match(
timeout_ms,
async fn() { self.actual.is_disabled() },
fn(value : Bool) { value },
false,
)
if matched {
return ()
}
let state = if actual { "disabled" } else { "enabled" }
let message = match msg {
Some(value) => value
None => "expected locator to be disabled but was " + state
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_be_checked(
self : Expect[Locator],
timeout? : Int,
msg? : String,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let (matched, actual) = wait_for_match(
timeout_ms,
async fn() { self.actual.is_checked() },
fn(value : Bool) { value },
false,
)
if matched {
return ()
}
let state = if actual { "checked" } else { "unchecked" }
let message = match msg {
Some(value) => value
None => "expected locator to be checked but was " + state
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_be_editable(
self : Expect[Locator],
timeout? : Int,
msg? : String,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let (matched, actual) = wait_for_match(
timeout_ms,
async fn() { self.actual.is_editable() },
fn(value : Bool) { value },
false,
)
if matched {
return ()
}
let state = if actual { "editable" } else { "read-only" }
let message = match msg {
Some(value) => value
None => "expected locator to be editable but was " + state
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_have_value(
self : Expect[Locator],
expected : String,
timeout? : Int,
msg? : String,
match_mode? : String,
normalize? : Bool,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let normalize_value = match normalize {
Some(value) => value
None => false
}
let mode = resolve_text_match_mode(TextMatchKind::Equal, match_mode)
let (matched, actual) = wait_for_match(
timeout_ms,
async fn() { self.actual.input_value() },
fn(value : String) { text_matches(value, expected, mode, normalize_value) },
"",
)
if matched {
return ()
}
let message = match msg {
Some(value) => value
None =>
"expected value to be \"" + expected + "\" but was \"" + actual + "\""
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_have_attribute(
self : Expect[Locator],
name : String,
expected? : String,
timeout? : Int,
msg? : String,
match_mode? : String,
normalize? : Bool,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let normalize_value = match normalize {
Some(value) => value
None => false
}
let mode = resolve_text_match_mode(TextMatchKind::Equal, match_mode)
let initial : String? = None
let (matched, actual) = wait_for_match(
timeout_ms,
async fn() { self.actual.get_attribute(name) },
fn(value : String?) {
match expected {
Some(exp) =>
match value {
Some(actual_value) =>
text_matches(actual_value, exp, mode, normalize_value)
None => false
}
None =>
match value {
Some(_) => true
None => false
}
}
},
initial,
)
if matched {
return ()
}
let actual_text = match actual {
Some(value) => value
None => "null"
}
let message = match msg {
Some(value) => value
None =>
match expected {
Some(exp) =>
"expected attribute \"" +
name +
"\" to match \"" +
exp +
"\" but was \"" +
actual_text +
"\""
None =>
"expected attribute \"" +
name +
"\" to be present but was \"" +
actual_text +
"\""
}
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_have_count(
self : Expect[Locator],
expected : Int,
timeout? : Int,
msg? : String,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let (matched, actual) = wait_for_match(
timeout_ms,
async fn() { self.actual.count() },
fn(value : Int) { value == expected },
0,
)
if matched {
return ()
}
let message = match msg {
Some(value) => value
None =>
"expected count " +
expected.to_string() +
" but was " +
actual.to_string()
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_have_url(
self : Expect[Page],
expected : String,
timeout? : Int,
msg? : String,
match_mode? : String,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let mode = resolve_match_mode(expected, match_mode)
let initial = self.actual.url()
let (matched, actual) = wait_for_match(
timeout_ms,
fn() { self.actual.url() },
fn(value : String) { url_matches(value, expected, mode) },
initial,
)
if matched {
return ()
}
let message = match msg {
Some(value) => value
None =>
"expected url to match \"" + expected + "\" but was \"" + actual + "\""
}
assert_eq(matched, true, msg=message, loc~)
}
///|
#callsite(autofill(loc))
pub async fn Expect::to_have_title(
self : Expect[Page],
expected : String,
timeout? : Int,
msg? : String,
match_mode? : String,
normalize? : Bool,
loc~ : SourceLoc,
) -> Unit {
let timeout_ms = match timeout {
Some(value) => value
None => 5_000
}
let normalize_value = match normalize {
Some(value) => value
None => true
}
let mode = resolve_text_match_mode(TextMatchKind::Equal, match_mode)
let (matched, actual) = wait_for_match(
timeout_ms,
async fn() { self.actual.title() },
fn(value : String) { text_matches(value, expected, mode, normalize_value) },
"",
)
if matched {
return ()
}
let message = match msg {
Some(value) => value
None =>
"expected title to be \"" + expected + "\" but was \"" + actual + "\""
}
assert_eq(matched, true, msg=message, loc~)
}