1use serde::{Deserialize, Serialize};
14use std::time::Duration;
15
16use crate::result::{ProbarError, ProbarResult};
17
18pub const DEFAULT_TIMEOUT_MS: u64 = 5000;
20
21pub const DEFAULT_POLL_INTERVAL_MS: u64 = 50;
23
24#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
26pub struct Point {
27 pub x: f32,
29 pub y: f32,
31}
32
33impl Point {
34 #[must_use]
36 pub const fn new(x: f32, y: f32) -> Self {
37 Self { x, y }
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum Selector {
44 Css(String),
46 XPath(String),
48 Text(String),
50 TestId(String),
52 Entity(String),
54 CssWithText {
56 css: String,
58 text: String,
60 },
61 CanvasEntity {
63 entity: String,
65 },
66 Role {
71 role: String,
73 name: Option<String>,
75 },
76 Label(String),
78 Placeholder(String),
80 AltText(String),
82}
83
84impl Selector {
85 #[must_use]
87 pub fn css(selector: impl Into<String>) -> Self {
88 Self::Css(selector.into())
89 }
90
91 #[must_use]
93 pub fn test_id(id: impl Into<String>) -> Self {
94 Self::TestId(id.into())
95 }
96
97 #[must_use]
99 pub fn text(text: impl Into<String>) -> Self {
100 Self::Text(text.into())
101 }
102
103 #[must_use]
105 pub fn entity(name: impl Into<String>) -> Self {
106 Self::Entity(name.into())
107 }
108
109 #[must_use]
117 pub fn role(role: impl Into<String>) -> Self {
118 Self::Role {
119 role: role.into(),
120 name: None,
121 }
122 }
123
124 #[must_use]
126 pub fn role_with_name(role: impl Into<String>, name: impl Into<String>) -> Self {
127 Self::Role {
128 role: role.into(),
129 name: Some(name.into()),
130 }
131 }
132
133 #[must_use]
137 pub fn label(text: impl Into<String>) -> Self {
138 Self::Label(text.into())
139 }
140
141 #[must_use]
145 pub fn placeholder(text: impl Into<String>) -> Self {
146 Self::Placeholder(text.into())
147 }
148
149 #[must_use]
153 pub fn alt_text(text: impl Into<String>) -> Self {
154 Self::AltText(text.into())
155 }
156
157 #[must_use]
159 pub fn to_query(&self) -> String {
160 match self {
161 Self::Css(s) => format!("document.querySelector({s:?})"),
162 Self::XPath(s) => {
163 format!("document.evaluate({s:?}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue")
164 }
165 Self::Text(t) => {
166 format!("Array.from(document.querySelectorAll('*')).find(el => el.textContent.includes({t:?}))")
167 }
168 Self::TestId(id) => format!("document.querySelector('[data-testid={id:?}]')"),
169 Self::Entity(name) => format!("window.__wasm_get_entity({name:?})"),
170 Self::CssWithText { css, text } => {
171 format!("Array.from(document.querySelectorAll({css:?})).find(el => el.textContent.includes({text:?}))")
172 }
173 Self::CanvasEntity { entity } => format!("window.__wasm_get_canvas_entity({entity:?})"),
174 Self::Role { role, name } => {
176 if let Some(n) = name {
177 format!(
178 "Array.from(document.querySelectorAll('[role={role:?}]')).find(el => el.textContent.includes({n:?}) || el.getAttribute('aria-label')?.includes({n:?}))"
179 )
180 } else {
181 format!("document.querySelector('[role={role:?}]')")
182 }
183 }
184 Self::Label(text) => {
185 format!(
186 "(function() {{ const label = Array.from(document.querySelectorAll('label')).find(l => l.textContent.includes({text:?})); if (label && label.htmlFor) return document.getElementById(label.htmlFor); if (label) return label.querySelector('input, textarea, select'); return null; }})()"
187 )
188 }
189 Self::Placeholder(text) => {
190 format!("document.querySelector('[placeholder*={text:?}]')")
191 }
192 Self::AltText(text) => {
193 format!("document.querySelector('img[alt*={text:?}]')")
194 }
195 }
196 }
197
198 #[must_use]
200 pub fn to_count_query(&self) -> String {
201 match self {
202 Self::Css(s) => format!("document.querySelectorAll({s:?}).length"),
203 Self::XPath(s) => {
204 format!("document.evaluate({s:?}, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotLength")
205 }
206 Self::Text(t) => {
207 format!("Array.from(document.querySelectorAll('*')).filter(el => el.textContent.includes({t:?})).length")
208 }
209 Self::TestId(id) => format!("document.querySelectorAll('[data-testid={id:?}]').length"),
210 Self::Entity(name) => format!("window.__wasm_count_entities({name:?})"),
211 Self::CssWithText { css, text } => {
212 format!("Array.from(document.querySelectorAll({css:?})).filter(el => el.textContent.includes({text:?})).length")
213 }
214 Self::CanvasEntity { entity } => {
215 format!("window.__wasm_count_canvas_entities({entity:?})")
216 }
217 Self::Role { role, name } => {
219 if let Some(n) = name {
220 format!(
221 "Array.from(document.querySelectorAll('[role={role:?}]')).filter(el => el.textContent.includes({n:?}) || el.getAttribute('aria-label')?.includes({n:?})).length"
222 )
223 } else {
224 format!("document.querySelectorAll('[role={role:?}]').length")
225 }
226 }
227 Self::Label(text) => {
228 format!(
229 "Array.from(document.querySelectorAll('label')).filter(l => l.textContent.includes({text:?})).length"
230 )
231 }
232 Self::Placeholder(text) => {
233 format!("document.querySelectorAll('[placeholder*={text:?}]').length")
234 }
235 Self::AltText(text) => {
236 format!("document.querySelectorAll('img[alt*={text:?}]').length")
237 }
238 }
239 }
240}
241
242#[derive(Debug, Clone)]
244pub struct DragOperation {
245 pub target: Point,
247 pub steps: u32,
249 pub duration: Duration,
251}
252
253impl DragOperation {
254 #[must_use]
256 pub fn to(target: Point) -> Self {
257 Self {
258 target,
259 steps: 10,
260 duration: Duration::from_millis(500),
261 }
262 }
263
264 #[must_use]
266 pub const fn steps(mut self, steps: u32) -> Self {
267 self.steps = steps;
268 self
269 }
270
271 #[must_use]
273 pub const fn duration(mut self, duration: Duration) -> Self {
274 self.duration = duration;
275 self
276 }
277}
278
279#[derive(Debug, Clone)]
281pub struct LocatorOptions {
282 pub timeout: Duration,
284 pub poll_interval: Duration,
286 pub strict: bool,
288 pub visible: bool,
290}
291
292impl Default for LocatorOptions {
293 fn default() -> Self {
294 Self {
295 timeout: Duration::from_millis(DEFAULT_TIMEOUT_MS),
296 poll_interval: Duration::from_millis(DEFAULT_POLL_INTERVAL_MS),
297 strict: true,
298 visible: true,
299 }
300 }
301}
302
303#[derive(Debug, Clone, Default)]
309pub struct FilterOptions {
310 pub has: Option<Box<Locator>>,
312 pub has_text: Option<String>,
314 pub has_not: Option<Box<Locator>>,
316 pub has_not_text: Option<String>,
318}
319
320impl FilterOptions {
321 #[must_use]
323 pub fn new() -> Self {
324 Self::default()
325 }
326
327 #[must_use]
329 pub fn has(mut self, locator: Locator) -> Self {
330 self.has = Some(Box::new(locator));
331 self
332 }
333
334 #[must_use]
336 pub fn has_text(mut self, text: impl Into<String>) -> Self {
337 self.has_text = Some(text.into());
338 self
339 }
340
341 #[must_use]
343 pub fn has_not(mut self, locator: Locator) -> Self {
344 self.has_not = Some(Box::new(locator));
345 self
346 }
347
348 #[must_use]
350 pub fn has_not_text(mut self, text: impl Into<String>) -> Self {
351 self.has_not_text = Some(text.into());
352 self
353 }
354}
355
356#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
362pub enum MouseButton {
363 #[default]
365 Left,
366 Right,
368 Middle,
370}
371
372#[derive(Debug, Clone, Default)]
374pub struct ClickOptions {
375 pub button: MouseButton,
377 pub click_count: u32,
379 pub position: Option<Point>,
381 pub modifiers: Vec<KeyModifier>,
383}
384
385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
387pub enum KeyModifier {
388 Alt,
390 Control,
392 Meta,
394 Shift,
396}
397
398impl ClickOptions {
399 #[must_use]
401 pub fn new() -> Self {
402 Self {
403 button: MouseButton::Left,
404 click_count: 1,
405 position: None,
406 modifiers: Vec::new(),
407 }
408 }
409
410 #[must_use]
412 pub fn button(mut self, button: MouseButton) -> Self {
413 self.button = button;
414 self
415 }
416
417 #[must_use]
419 pub fn click_count(mut self, count: u32) -> Self {
420 self.click_count = count;
421 self
422 }
423
424 #[must_use]
426 pub fn position(mut self, pos: Point) -> Self {
427 self.position = Some(pos);
428 self
429 }
430
431 #[must_use]
433 pub fn modifier(mut self, modifier: KeyModifier) -> Self {
434 self.modifiers.push(modifier);
435 self
436 }
437}
438
439#[derive(Debug, Clone)]
443pub struct Locator {
444 selector: Selector,
446 options: LocatorOptions,
448}
449
450impl Locator {
451 #[must_use]
453 pub fn new(selector: impl Into<String>) -> Self {
454 Self {
455 selector: Selector::Css(selector.into()),
456 options: LocatorOptions::default(),
457 }
458 }
459
460 #[must_use]
462 pub fn from_selector(selector: Selector) -> Self {
463 Self {
464 selector,
465 options: LocatorOptions::default(),
466 }
467 }
468
469 #[must_use]
473 pub fn with_text(self, text: impl Into<String>) -> Self {
474 let new_selector = match self.selector {
475 Selector::Css(css) => Selector::CssWithText {
476 css,
477 text: text.into(),
478 },
479 other => {
480 let _ = text.into();
483 other
484 }
485 };
486 Self {
487 selector: new_selector,
488 options: self.options,
489 }
490 }
491
492 #[must_use]
496 pub fn entity(self, name: impl Into<String>) -> Self {
497 Self {
498 selector: Selector::CanvasEntity {
499 entity: name.into(),
500 },
501 options: self.options,
502 }
503 }
504
505 #[must_use]
507 pub const fn with_timeout(mut self, timeout: Duration) -> Self {
508 self.options.timeout = timeout;
509 self
510 }
511
512 #[must_use]
514 pub const fn with_strict(mut self, strict: bool) -> Self {
515 self.options.strict = strict;
516 self
517 }
518
519 #[must_use]
521 pub const fn with_visible(mut self, visible: bool) -> Self {
522 self.options.visible = visible;
523 self
524 }
525
526 #[must_use]
528 pub const fn selector(&self) -> &Selector {
529 &self.selector
530 }
531
532 #[must_use]
534 pub const fn options(&self) -> &LocatorOptions {
535 &self.options
536 }
537
538 pub fn click(&self) -> ProbarResult<LocatorAction> {
544 Ok(LocatorAction::Click {
545 locator: self.clone(),
546 })
547 }
548
549 pub fn double_click(&self) -> ProbarResult<LocatorAction> {
555 Ok(LocatorAction::DoubleClick {
556 locator: self.clone(),
557 })
558 }
559
560 #[must_use]
564 pub fn drag_to(&self, target: &Point) -> DragBuilder {
565 DragBuilder {
566 locator: self.clone(),
567 target: *target,
568 steps: 10,
569 duration: Duration::from_millis(500),
570 }
571 }
572
573 pub fn fill(&self, text: impl Into<String>) -> ProbarResult<LocatorAction> {
579 Ok(LocatorAction::Fill {
580 locator: self.clone(),
581 text: text.into(),
582 })
583 }
584
585 pub fn text_content(&self) -> ProbarResult<LocatorQuery> {
591 Ok(LocatorQuery::TextContent {
592 locator: self.clone(),
593 })
594 }
595
596 pub fn is_visible(&self) -> ProbarResult<LocatorQuery> {
598 Ok(LocatorQuery::IsVisible {
599 locator: self.clone(),
600 })
601 }
602
603 pub fn bounding_box(&self) -> ProbarResult<LocatorQuery> {
605 Ok(LocatorQuery::BoundingBox {
606 locator: self.clone(),
607 })
608 }
609
610 pub fn wait_for_visible(&self) -> ProbarResult<LocatorAction> {
612 Ok(LocatorAction::WaitForVisible {
613 locator: self.clone(),
614 })
615 }
616
617 pub fn wait_for_hidden(&self) -> ProbarResult<LocatorAction> {
619 Ok(LocatorAction::WaitForHidden {
620 locator: self.clone(),
621 })
622 }
623
624 pub fn count(&self) -> ProbarResult<LocatorQuery> {
626 Ok(LocatorQuery::Count {
627 locator: self.clone(),
628 })
629 }
630
631 #[must_use]
639 pub fn filter(self, options: FilterOptions) -> Self {
640 if let Some(text) = options.has_text {
642 return self.with_text(text);
643 }
644 self
647 }
648
649 #[must_use]
653 pub fn and(self, other: Locator) -> Self {
654 let new_selector = match (&self.selector, &other.selector) {
656 (Selector::Css(a), Selector::Css(b)) => Selector::Css(format!("{a}{b}")),
657 _ => self.selector, };
659 Self {
660 selector: new_selector,
661 options: self.options,
662 }
663 }
664
665 #[must_use]
669 pub fn or(self, other: Locator) -> Self {
670 let new_selector = match (&self.selector, &other.selector) {
672 (Selector::Css(a), Selector::Css(b)) => Selector::Css(format!("{a}, {b}")),
673 _ => self.selector, };
675 Self {
676 selector: new_selector,
677 options: self.options,
678 }
679 }
680
681 #[must_use]
685 pub fn first(self) -> Self {
686 let new_selector = match self.selector {
688 Selector::Css(s) => Selector::Css(format!("{s}:first-child")),
689 other => other,
690 };
691 Self {
692 selector: new_selector,
693 options: self.options,
694 }
695 }
696
697 #[must_use]
701 pub fn last(self) -> Self {
702 let new_selector = match self.selector {
704 Selector::Css(s) => Selector::Css(format!("{s}:last-child")),
705 other => other,
706 };
707 Self {
708 selector: new_selector,
709 options: self.options,
710 }
711 }
712
713 #[must_use]
717 pub fn nth(self, index: usize) -> Self {
718 let new_selector = match self.selector {
720 Selector::Css(s) => Selector::Css(format!("{s}:nth-child({})", index + 1)),
721 other => other,
722 };
723 Self {
724 selector: new_selector,
725 options: self.options,
726 }
727 }
728
729 pub fn right_click(&self) -> ProbarResult<LocatorAction> {
737 Ok(LocatorAction::RightClick {
738 locator: self.clone(),
739 })
740 }
741
742 pub fn click_with_options(&self, options: ClickOptions) -> ProbarResult<LocatorAction> {
746 Ok(LocatorAction::ClickWithOptions {
747 locator: self.clone(),
748 options,
749 })
750 }
751
752 pub fn hover(&self) -> ProbarResult<LocatorAction> {
756 Ok(LocatorAction::Hover {
757 locator: self.clone(),
758 })
759 }
760
761 pub fn focus(&self) -> ProbarResult<LocatorAction> {
765 Ok(LocatorAction::Focus {
766 locator: self.clone(),
767 })
768 }
769
770 pub fn blur(&self) -> ProbarResult<LocatorAction> {
774 Ok(LocatorAction::Blur {
775 locator: self.clone(),
776 })
777 }
778
779 pub fn check(&self) -> ProbarResult<LocatorAction> {
783 Ok(LocatorAction::Check {
784 locator: self.clone(),
785 })
786 }
787
788 pub fn uncheck(&self) -> ProbarResult<LocatorAction> {
792 Ok(LocatorAction::Uncheck {
793 locator: self.clone(),
794 })
795 }
796
797 pub fn scroll_into_view(&self) -> ProbarResult<LocatorAction> {
801 Ok(LocatorAction::ScrollIntoView {
802 locator: self.clone(),
803 })
804 }
805
806 #[must_use]
812 pub fn by_role(role: impl Into<String>) -> Self {
813 Self::from_selector(Selector::role(role))
814 }
815
816 #[must_use]
818 pub fn by_role_with_name(role: impl Into<String>, name: impl Into<String>) -> Self {
819 Self::from_selector(Selector::role_with_name(role, name))
820 }
821
822 #[must_use]
824 pub fn by_label(text: impl Into<String>) -> Self {
825 Self::from_selector(Selector::label(text))
826 }
827
828 #[must_use]
830 pub fn by_placeholder(text: impl Into<String>) -> Self {
831 Self::from_selector(Selector::placeholder(text))
832 }
833
834 #[must_use]
836 pub fn by_alt_text(text: impl Into<String>) -> Self {
837 Self::from_selector(Selector::alt_text(text))
838 }
839
840 #[must_use]
842 pub fn by_test_id(id: impl Into<String>) -> Self {
843 Self::from_selector(Selector::test_id(id))
844 }
845
846 #[must_use]
848 pub fn by_text(text: impl Into<String>) -> Self {
849 Self::from_selector(Selector::text(text))
850 }
851}
852
853#[derive(Debug, Clone)]
855pub struct DragBuilder {
856 locator: Locator,
857 target: Point,
858 steps: u32,
859 duration: Duration,
860}
861
862impl DragBuilder {
863 #[must_use]
865 pub const fn steps(mut self, steps: u32) -> Self {
866 self.steps = steps;
867 self
868 }
869
870 #[must_use]
872 pub const fn duration(mut self, duration: Duration) -> Self {
873 self.duration = duration;
874 self
875 }
876
877 pub fn build(self) -> LocatorAction {
879 LocatorAction::Drag {
880 locator: self.locator,
881 target: self.target,
882 steps: self.steps,
883 duration: self.duration,
884 }
885 }
886}
887
888#[derive(Debug, Clone)]
890pub enum LocatorAction {
891 Click {
893 locator: Locator,
895 },
896 DoubleClick {
898 locator: Locator,
900 },
901 Drag {
903 locator: Locator,
905 target: Point,
907 steps: u32,
909 duration: Duration,
911 },
912 Fill {
914 locator: Locator,
916 text: String,
918 },
919 WaitForVisible {
921 locator: Locator,
923 },
924 WaitForHidden {
926 locator: Locator,
928 },
929 RightClick {
934 locator: Locator,
936 },
937 ClickWithOptions {
939 locator: Locator,
941 options: ClickOptions,
943 },
944 Hover {
946 locator: Locator,
948 },
949 Focus {
951 locator: Locator,
953 },
954 Blur {
956 locator: Locator,
958 },
959 Check {
961 locator: Locator,
963 },
964 Uncheck {
966 locator: Locator,
968 },
969 ScrollIntoView {
971 locator: Locator,
973 },
974}
975
976impl LocatorAction {
977 #[must_use]
979 pub fn locator(&self) -> &Locator {
980 match self {
981 Self::Click { locator }
982 | Self::DoubleClick { locator }
983 | Self::Drag { locator, .. }
984 | Self::Fill { locator, .. }
985 | Self::WaitForVisible { locator }
986 | Self::WaitForHidden { locator }
987 | Self::RightClick { locator }
988 | Self::ClickWithOptions { locator, .. }
989 | Self::Hover { locator }
990 | Self::Focus { locator }
991 | Self::Blur { locator }
992 | Self::Check { locator }
993 | Self::Uncheck { locator }
994 | Self::ScrollIntoView { locator } => locator,
995 }
996 }
997}
998
999#[derive(Debug, Clone)]
1001pub enum LocatorQuery {
1002 TextContent {
1004 locator: Locator,
1006 },
1007 IsVisible {
1009 locator: Locator,
1011 },
1012 BoundingBox {
1014 locator: Locator,
1016 },
1017 Count {
1019 locator: Locator,
1021 },
1022}
1023
1024impl LocatorQuery {
1025 #[must_use]
1027 pub const fn locator(&self) -> &Locator {
1028 match self {
1029 Self::TextContent { locator }
1030 | Self::IsVisible { locator }
1031 | Self::BoundingBox { locator }
1032 | Self::Count { locator } => locator,
1033 }
1034 }
1035}
1036
1037#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1039pub struct BoundingBox {
1040 pub x: f32,
1042 pub y: f32,
1044 pub width: f32,
1046 pub height: f32,
1048}
1049
1050impl BoundingBox {
1051 #[must_use]
1053 pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
1054 Self {
1055 x,
1056 y,
1057 width,
1058 height,
1059 }
1060 }
1061
1062 #[must_use]
1064 pub fn center(&self) -> Point {
1065 Point::new(self.x + self.width / 2.0, self.y + self.height / 2.0)
1066 }
1067
1068 #[must_use]
1070 pub fn contains(&self, point: &Point) -> bool {
1071 point.x >= self.x
1072 && point.x <= self.x + self.width
1073 && point.y >= self.y
1074 && point.y <= self.y + self.height
1075 }
1076}
1077
1078#[derive(Debug, Clone)]
1082pub struct Expect {
1083 locator: Locator,
1084}
1085
1086impl Expect {
1087 #[must_use]
1089 pub const fn new(locator: Locator) -> Self {
1090 Self { locator }
1091 }
1092
1093 pub fn to_have_text(&self, expected: impl Into<String>) -> ExpectAssertion {
1095 ExpectAssertion::HasText {
1096 locator: self.locator.clone(),
1097 expected: expected.into(),
1098 }
1099 }
1100
1101 pub fn to_be_visible(&self) -> ExpectAssertion {
1103 ExpectAssertion::IsVisible {
1104 locator: self.locator.clone(),
1105 }
1106 }
1107
1108 pub fn to_be_hidden(&self) -> ExpectAssertion {
1110 ExpectAssertion::IsHidden {
1111 locator: self.locator.clone(),
1112 }
1113 }
1114
1115 pub fn to_have_count(&self, count: usize) -> ExpectAssertion {
1117 ExpectAssertion::HasCount {
1118 locator: self.locator.clone(),
1119 expected: count,
1120 }
1121 }
1122
1123 pub fn to_contain_text(&self, text: impl Into<String>) -> ExpectAssertion {
1125 ExpectAssertion::ContainsText {
1126 locator: self.locator.clone(),
1127 expected: text.into(),
1128 }
1129 }
1130
1131 pub fn to_be_enabled(&self) -> ExpectAssertion {
1137 ExpectAssertion::IsEnabled {
1138 locator: self.locator.clone(),
1139 }
1140 }
1141
1142 pub fn to_be_disabled(&self) -> ExpectAssertion {
1144 ExpectAssertion::IsDisabled {
1145 locator: self.locator.clone(),
1146 }
1147 }
1148
1149 pub fn to_be_checked(&self) -> ExpectAssertion {
1151 ExpectAssertion::IsChecked {
1152 locator: self.locator.clone(),
1153 }
1154 }
1155
1156 pub fn to_be_editable(&self) -> ExpectAssertion {
1158 ExpectAssertion::IsEditable {
1159 locator: self.locator.clone(),
1160 }
1161 }
1162
1163 pub fn to_be_focused(&self) -> ExpectAssertion {
1165 ExpectAssertion::IsFocused {
1166 locator: self.locator.clone(),
1167 }
1168 }
1169
1170 pub fn to_be_empty(&self) -> ExpectAssertion {
1172 ExpectAssertion::IsEmpty {
1173 locator: self.locator.clone(),
1174 }
1175 }
1176
1177 pub fn to_have_value(&self, value: impl Into<String>) -> ExpectAssertion {
1179 ExpectAssertion::HasValue {
1180 locator: self.locator.clone(),
1181 expected: value.into(),
1182 }
1183 }
1184
1185 pub fn to_have_css(
1187 &self,
1188 property: impl Into<String>,
1189 value: impl Into<String>,
1190 ) -> ExpectAssertion {
1191 ExpectAssertion::HasCss {
1192 locator: self.locator.clone(),
1193 property: property.into(),
1194 expected: value.into(),
1195 }
1196 }
1197
1198 pub fn to_have_class(&self, class: impl Into<String>) -> ExpectAssertion {
1200 ExpectAssertion::HasClass {
1201 locator: self.locator.clone(),
1202 expected: class.into(),
1203 }
1204 }
1205
1206 pub fn to_have_id(&self, id: impl Into<String>) -> ExpectAssertion {
1208 ExpectAssertion::HasId {
1209 locator: self.locator.clone(),
1210 expected: id.into(),
1211 }
1212 }
1213
1214 pub fn to_have_attribute(
1216 &self,
1217 name: impl Into<String>,
1218 value: impl Into<String>,
1219 ) -> ExpectAssertion {
1220 ExpectAssertion::HasAttribute {
1221 locator: self.locator.clone(),
1222 name: name.into(),
1223 expected: value.into(),
1224 }
1225 }
1226}
1227
1228#[derive(Debug, Clone)]
1230pub enum ExpectAssertion {
1231 HasText {
1233 locator: Locator,
1235 expected: String,
1237 },
1238 IsVisible {
1240 locator: Locator,
1242 },
1243 IsHidden {
1245 locator: Locator,
1247 },
1248 HasCount {
1250 locator: Locator,
1252 expected: usize,
1254 },
1255 ContainsText {
1257 locator: Locator,
1259 expected: String,
1261 },
1262 IsEnabled {
1267 locator: Locator,
1269 },
1270 IsDisabled {
1272 locator: Locator,
1274 },
1275 IsChecked {
1277 locator: Locator,
1279 },
1280 IsEditable {
1282 locator: Locator,
1284 },
1285 IsFocused {
1287 locator: Locator,
1289 },
1290 IsEmpty {
1292 locator: Locator,
1294 },
1295 HasValue {
1297 locator: Locator,
1299 expected: String,
1301 },
1302 HasCss {
1304 locator: Locator,
1306 property: String,
1308 expected: String,
1310 },
1311 HasClass {
1313 locator: Locator,
1315 expected: String,
1317 },
1318 HasId {
1320 locator: Locator,
1322 expected: String,
1324 },
1325 HasAttribute {
1327 locator: Locator,
1329 name: String,
1331 expected: String,
1333 },
1334}
1335
1336impl ExpectAssertion {
1337 pub fn validate(&self, actual: &str) -> ProbarResult<()> {
1343 match self {
1344 Self::HasText { expected, .. } => {
1345 if actual == expected {
1346 Ok(())
1347 } else {
1348 Err(ProbarError::AssertionError {
1349 message: format!("Expected text '{expected}' but got '{actual}'"),
1350 })
1351 }
1352 }
1353 Self::ContainsText { expected, .. } => {
1354 if actual.contains(expected) {
1355 Ok(())
1356 } else {
1357 Err(ProbarError::AssertionError {
1358 message: format!(
1359 "Expected text to contain '{expected}' but got '{actual}'"
1360 ),
1361 })
1362 }
1363 }
1364 Self::HasValue { expected, .. } => {
1365 if actual == expected {
1366 Ok(())
1367 } else {
1368 Err(ProbarError::AssertionError {
1369 message: format!("Expected value '{expected}' but got '{actual}'"),
1370 })
1371 }
1372 }
1373 Self::HasClass { expected, .. } => {
1374 if actual.split_whitespace().any(|c| c == expected) {
1375 Ok(())
1376 } else {
1377 Err(ProbarError::AssertionError {
1378 message: format!("Expected class '{expected}' but got '{actual}'"),
1379 })
1380 }
1381 }
1382 Self::HasId { expected, .. } => {
1383 if actual == expected {
1384 Ok(())
1385 } else {
1386 Err(ProbarError::AssertionError {
1387 message: format!("Expected id '{expected}' but got '{actual}'"),
1388 })
1389 }
1390 }
1391 Self::HasAttribute { name, expected, .. } => {
1392 if actual == expected {
1393 Ok(())
1394 } else {
1395 Err(ProbarError::AssertionError {
1396 message: format!(
1397 "Expected attribute '{name}' to be '{expected}' but got '{actual}'"
1398 ),
1399 })
1400 }
1401 }
1402 Self::IsVisible { .. }
1404 | Self::IsHidden { .. }
1405 | Self::HasCount { .. }
1406 | Self::IsEnabled { .. }
1407 | Self::IsDisabled { .. }
1408 | Self::IsChecked { .. }
1409 | Self::IsEditable { .. }
1410 | Self::IsFocused { .. }
1411 | Self::IsEmpty { .. }
1412 | Self::HasCss { .. } => Ok(()),
1413 }
1414 }
1415
1416 pub fn validate_count(&self, actual: usize) -> ProbarResult<()> {
1422 match self {
1423 Self::HasCount { expected, .. } => {
1424 if actual == *expected {
1425 Ok(())
1426 } else {
1427 Err(ProbarError::AssertionError {
1428 message: format!("Expected count {expected} but got {actual}"),
1429 })
1430 }
1431 }
1432 _ => Ok(()),
1433 }
1434 }
1435
1436 pub fn validate_state(&self, actual: bool) -> ProbarResult<()> {
1442 match self {
1443 Self::IsEnabled { .. } => {
1444 if actual {
1445 Ok(())
1446 } else {
1447 Err(ProbarError::AssertionError {
1448 message: "Expected element to be enabled but it was disabled".to_string(),
1449 })
1450 }
1451 }
1452 Self::IsDisabled { .. } => {
1453 if actual {
1454 Ok(())
1455 } else {
1456 Err(ProbarError::AssertionError {
1457 message: "Expected element to be disabled but it was enabled".to_string(),
1458 })
1459 }
1460 }
1461 Self::IsChecked { .. } => {
1462 if actual {
1463 Ok(())
1464 } else {
1465 Err(ProbarError::AssertionError {
1466 message: "Expected element to be checked but it was not".to_string(),
1467 })
1468 }
1469 }
1470 Self::IsEditable { .. } => {
1471 if actual {
1472 Ok(())
1473 } else {
1474 Err(ProbarError::AssertionError {
1475 message: "Expected element to be editable but it was not".to_string(),
1476 })
1477 }
1478 }
1479 Self::IsFocused { .. } => {
1480 if actual {
1481 Ok(())
1482 } else {
1483 Err(ProbarError::AssertionError {
1484 message: "Expected element to be focused but it was not".to_string(),
1485 })
1486 }
1487 }
1488 Self::IsEmpty { .. } => {
1489 if actual {
1490 Ok(())
1491 } else {
1492 Err(ProbarError::AssertionError {
1493 message: "Expected element to be empty but it was not".to_string(),
1494 })
1495 }
1496 }
1497 Self::IsVisible { .. } => {
1498 if actual {
1499 Ok(())
1500 } else {
1501 Err(ProbarError::AssertionError {
1502 message: "Expected element to be visible but it was hidden".to_string(),
1503 })
1504 }
1505 }
1506 Self::IsHidden { .. } => {
1507 if actual {
1508 Ok(())
1509 } else {
1510 Err(ProbarError::AssertionError {
1511 message: "Expected element to be hidden but it was visible".to_string(),
1512 })
1513 }
1514 }
1515 _ => Ok(()),
1516 }
1517 }
1518}
1519
1520#[must_use]
1524pub fn expect(locator: Locator) -> Expect {
1525 Expect::new(locator)
1526}
1527
1528#[cfg(test)]
1529#[allow(
1530 clippy::unwrap_used,
1531 clippy::expect_used,
1532 clippy::panic,
1533 clippy::default_trait_access
1534)]
1535mod tests {
1536 use super::*;
1537
1538 mod selector_tests {
1543 use super::*;
1544
1545 #[test]
1546 fn test_css_selector() {
1547 let selector = Selector::css("button.primary");
1548 let query = selector.to_query();
1549 assert!(query.contains("querySelector"));
1550 assert!(query.contains("button.primary"));
1551 }
1552
1553 #[test]
1554 fn test_test_id_selector() {
1555 let selector = Selector::test_id("score");
1556 let query = selector.to_query();
1557 assert!(query.contains("data-testid"));
1558 assert!(query.contains("score"));
1559 }
1560
1561 #[test]
1562 fn test_text_selector() {
1563 let selector = Selector::text("Start Game");
1564 let query = selector.to_query();
1565 assert!(query.contains("textContent"));
1566 assert!(query.contains("Start Game"));
1567 }
1568
1569 #[test]
1570 fn test_entity_selector() {
1571 let selector = Selector::entity("hero");
1572 let query = selector.to_query();
1573 assert!(query.contains("__wasm_get_entity"));
1574 assert!(query.contains("hero"));
1575 }
1576
1577 #[test]
1578 fn test_count_query() {
1579 let selector = Selector::css("button");
1580 let query = selector.to_count_query();
1581 assert!(query.contains("querySelectorAll"));
1582 assert!(query.contains(".length"));
1583 }
1584 }
1585
1586 mod locator_tests {
1587 use super::*;
1588
1589 #[test]
1590 fn test_locator_new() {
1591 let locator = Locator::new("button");
1592 assert!(matches!(locator.selector(), Selector::Css(_)));
1593 }
1594
1595 #[test]
1596 fn test_locator_with_text() {
1597 let locator = Locator::new("button").with_text("Start Game");
1598 assert!(matches!(locator.selector(), Selector::CssWithText { .. }));
1599 }
1600
1601 #[test]
1602 fn test_locator_entity() {
1603 let locator = Locator::new("canvas").entity("hero");
1604 assert!(matches!(locator.selector(), Selector::CanvasEntity { .. }));
1605 }
1606
1607 #[test]
1608 fn test_locator_timeout() {
1609 let locator = Locator::new("button").with_timeout(Duration::from_secs(10));
1610 assert_eq!(locator.options().timeout, Duration::from_secs(10));
1611 }
1612
1613 #[test]
1614 fn test_locator_strict_mode() {
1615 let locator = Locator::new("button").with_strict(false);
1616 assert!(!locator.options().strict);
1617 }
1618 }
1619
1620 mod action_tests {
1621 use super::*;
1622
1623 #[test]
1624 fn test_click_action() {
1625 let locator = Locator::new("button");
1626 let action = locator.click().unwrap();
1627 assert!(matches!(action, LocatorAction::Click { .. }));
1628 }
1629
1630 #[test]
1631 fn test_fill_action() {
1632 let locator = Locator::new("input");
1633 let action = locator.fill("test text").unwrap();
1634 assert!(matches!(action, LocatorAction::Fill { .. }));
1635 }
1636
1637 #[test]
1638 fn test_drag_builder() {
1639 let locator = Locator::new("canvas").entity("hero");
1640 let drag = locator
1641 .drag_to(&Point::new(500.0, 500.0))
1642 .steps(10)
1643 .duration(Duration::from_millis(500))
1644 .build();
1645 assert!(matches!(drag, LocatorAction::Drag { steps: 10, .. }));
1646 }
1647 }
1648
1649 mod query_tests {
1650 use super::*;
1651
1652 #[test]
1653 fn test_text_content_query() {
1654 let locator = Locator::new("[data-testid='score']");
1655 let query = locator.text_content().unwrap();
1656 assert!(matches!(query, LocatorQuery::TextContent { .. }));
1657 }
1658
1659 #[test]
1660 fn test_is_visible_query() {
1661 let locator = Locator::new("button");
1662 let query = locator.is_visible().unwrap();
1663 assert!(matches!(query, LocatorQuery::IsVisible { .. }));
1664 }
1665
1666 #[test]
1667 fn test_count_query() {
1668 let locator = Locator::new("li");
1669 let query = locator.count().unwrap();
1670 assert!(matches!(query, LocatorQuery::Count { .. }));
1671 }
1672 }
1673
1674 mod expect_tests {
1675 use super::*;
1676
1677 #[test]
1678 fn test_expect_to_have_text() {
1679 let locator = Locator::new("[data-testid='score']");
1680 let assertion = expect(locator).to_have_text("10");
1681 assert!(matches!(assertion, ExpectAssertion::HasText { .. }));
1682 }
1683
1684 #[test]
1685 fn test_expect_to_be_visible() {
1686 let locator = Locator::new("button");
1687 let assertion = expect(locator).to_be_visible();
1688 assert!(matches!(assertion, ExpectAssertion::IsVisible { .. }));
1689 }
1690
1691 #[test]
1692 fn test_expect_to_have_count() {
1693 let locator = Locator::new("li");
1694 let assertion = expect(locator).to_have_count(5);
1695 assert!(matches!(
1696 assertion,
1697 ExpectAssertion::HasCount { expected: 5, .. }
1698 ));
1699 }
1700
1701 #[test]
1702 fn test_validate_has_text_pass() {
1703 let locator = Locator::new("span");
1704 let assertion = expect(locator).to_have_text("10");
1705 assert!(assertion.validate("10").is_ok());
1706 }
1707
1708 #[test]
1709 fn test_validate_has_text_fail() {
1710 let locator = Locator::new("span");
1711 let assertion = expect(locator).to_have_text("10");
1712 assert!(assertion.validate("20").is_err());
1713 }
1714
1715 #[test]
1716 fn test_validate_contains_text_pass() {
1717 let locator = Locator::new("span");
1718 let assertion = expect(locator).to_contain_text("Score");
1719 assert!(assertion.validate("Score: 100").is_ok());
1720 }
1721
1722 #[test]
1723 fn test_validate_count_pass() {
1724 let locator = Locator::new("li");
1725 let assertion = expect(locator).to_have_count(3);
1726 assert!(assertion.validate_count(3).is_ok());
1727 }
1728
1729 #[test]
1730 fn test_validate_count_fail() {
1731 let locator = Locator::new("li");
1732 let assertion = expect(locator).to_have_count(3);
1733 assert!(assertion.validate_count(5).is_err());
1734 }
1735 }
1736
1737 mod point_tests {
1738 use super::*;
1739
1740 #[test]
1741 fn test_point_new() {
1742 let p = Point::new(100.0, 200.0);
1743 assert!((p.x - 100.0).abs() < f32::EPSILON);
1744 assert!((p.y - 200.0).abs() < f32::EPSILON);
1745 }
1746 }
1747
1748 mod bounding_box_tests {
1749 use super::*;
1750
1751 #[test]
1752 fn test_bounding_box_center() {
1753 let bbox = BoundingBox::new(0.0, 0.0, 100.0, 100.0);
1754 let center = bbox.center();
1755 assert!((center.x - 50.0).abs() < f32::EPSILON);
1756 assert!((center.y - 50.0).abs() < f32::EPSILON);
1757 }
1758
1759 #[test]
1760 fn test_bounding_box_contains() {
1761 let bbox = BoundingBox::new(0.0, 0.0, 100.0, 100.0);
1762 assert!(bbox.contains(&Point::new(50.0, 50.0)));
1763 assert!(!bbox.contains(&Point::new(150.0, 50.0)));
1764 }
1765 }
1766
1767 mod default_tests {
1768 use super::*;
1769
1770 #[test]
1771 fn test_default_timeout() {
1772 assert_eq!(DEFAULT_TIMEOUT_MS, 5000);
1773 }
1774
1775 #[test]
1776 fn test_default_poll_interval() {
1777 assert_eq!(DEFAULT_POLL_INTERVAL_MS, 50);
1778 }
1779
1780 #[test]
1781 fn test_locator_options_default() {
1782 let opts = LocatorOptions::default();
1783 assert_eq!(opts.timeout, Duration::from_millis(5000));
1784 assert!(opts.strict);
1785 assert!(opts.visible);
1786 }
1787 }
1788
1789 mod additional_selector_tests {
1790 use super::*;
1791
1792 #[test]
1793 fn test_xpath_selector_query() {
1794 let selector = Selector::XPath("//button[@id='test']".to_string());
1795 let query = selector.to_query();
1796 assert!(query.contains("evaluate"));
1797 assert!(query.contains("XPathResult"));
1798 }
1799
1800 #[test]
1801 fn test_xpath_selector_count_query() {
1802 let selector = Selector::XPath("//button".to_string());
1803 let query = selector.to_count_query();
1804 assert!(query.contains("SNAPSHOT"));
1805 assert!(query.contains("snapshotLength"));
1806 }
1807
1808 #[test]
1809 fn test_css_with_text_selector() {
1810 let selector = Selector::CssWithText {
1811 css: "button".to_string(),
1812 text: "Click Me".to_string(),
1813 };
1814 let query = selector.to_query();
1815 assert!(query.contains("querySelectorAll"));
1816 assert!(query.contains("textContent"));
1817 }
1818
1819 #[test]
1820 fn test_css_with_text_count_query() {
1821 let selector = Selector::CssWithText {
1822 css: "button".to_string(),
1823 text: "Click".to_string(),
1824 };
1825 let query = selector.to_count_query();
1826 assert!(query.contains("filter"));
1827 assert!(query.contains(".length"));
1828 }
1829
1830 #[test]
1831 fn test_canvas_entity_selector() {
1832 let selector = Selector::CanvasEntity {
1833 entity: "player".to_string(),
1834 };
1835 let query = selector.to_query();
1836 assert!(query.contains("__wasm_get_canvas_entity"));
1837 }
1838
1839 #[test]
1840 fn test_canvas_entity_count_query() {
1841 let selector = Selector::CanvasEntity {
1842 entity: "enemy".to_string(),
1843 };
1844 let query = selector.to_count_query();
1845 assert!(query.contains("__wasm_count_canvas_entities"));
1846 }
1847
1848 #[test]
1849 fn test_text_selector_count_query() {
1850 let selector = Selector::text("Hello");
1851 let query = selector.to_count_query();
1852 assert!(query.contains("filter"));
1853 assert!(query.contains("length"));
1854 }
1855
1856 #[test]
1857 fn test_entity_count_query() {
1858 let selector = Selector::entity("player");
1859 let query = selector.to_count_query();
1860 assert!(query.contains("__wasm_count_entities"));
1861 }
1862 }
1863
1864 mod additional_drag_tests {
1865 use super::*;
1866
1867 #[test]
1868 fn test_drag_operation_defaults() {
1869 let drag = DragOperation::to(Point::new(100.0, 100.0));
1870 assert_eq!(drag.steps, 10);
1871 assert_eq!(drag.duration, Duration::from_millis(500));
1872 }
1873
1874 #[test]
1875 fn test_drag_operation_custom_steps() {
1876 let drag = DragOperation::to(Point::new(100.0, 100.0)).steps(20);
1877 assert_eq!(drag.steps, 20);
1878 }
1879
1880 #[test]
1881 fn test_drag_operation_custom_duration() {
1882 let drag = DragOperation::to(Point::new(100.0, 100.0)).duration(Duration::from_secs(1));
1883 assert_eq!(drag.duration, Duration::from_secs(1));
1884 }
1885 }
1886
1887 mod additional_locator_tests {
1888 use super::*;
1889
1890 #[test]
1891 fn test_locator_bounding_box() {
1892 let locator = Locator::new("button");
1893 let query = locator.bounding_box().unwrap();
1894 assert!(matches!(query, LocatorQuery::BoundingBox { .. }));
1895 }
1896
1897 #[test]
1898 fn test_locator_from_selector() {
1899 let selector = Selector::XPath("//button[@id='submit']".to_string());
1900 let locator = Locator::from_selector(selector);
1901 assert!(matches!(locator.selector(), Selector::XPath(_)));
1902 }
1903
1904 #[test]
1905 fn test_locator_with_text_non_css() {
1906 let locator =
1908 Locator::from_selector(Selector::Entity("hero".to_string())).with_text("ignored");
1909 assert!(matches!(locator.selector(), Selector::Entity(_)));
1910 }
1911
1912 #[test]
1913 fn test_locator_with_visible() {
1914 let locator = Locator::new("button").with_visible(false);
1915 assert!(!locator.options().visible);
1916 }
1917
1918 #[test]
1919 fn test_locator_double_click() {
1920 let locator = Locator::new("button");
1921 let action = locator.double_click().unwrap();
1922 assert!(matches!(action, LocatorAction::DoubleClick { .. }));
1923 }
1924
1925 #[test]
1926 fn test_locator_wait_for_visible() {
1927 let locator = Locator::new("button");
1928 let action = locator.wait_for_visible().unwrap();
1929 assert!(matches!(action, LocatorAction::WaitForVisible { .. }));
1930 }
1931
1932 #[test]
1933 fn test_locator_wait_for_hidden() {
1934 let locator = Locator::new("button");
1935 let action = locator.wait_for_hidden().unwrap();
1936 assert!(matches!(action, LocatorAction::WaitForHidden { .. }));
1937 }
1938
1939 #[test]
1940 fn test_locator_action_locator_accessor() {
1941 let locator = Locator::new("button");
1942 let action = locator.click().unwrap();
1943 let _ = action.locator(); assert!(matches!(action, LocatorAction::Click { .. }));
1945 }
1946
1947 #[test]
1948 fn test_locator_query_locator_accessor() {
1949 let locator = Locator::new("button");
1950 let query = locator.count().unwrap();
1951 let accessed = query.locator();
1952 assert!(matches!(accessed.selector(), Selector::Css(_)));
1953 }
1954
1955 #[test]
1956 fn test_selector_to_count_query_all_variants() {
1957 let xpath = Selector::XPath("//button".to_string());
1959 assert!(xpath.to_count_query().contains("snapshotLength"));
1960
1961 let text = Selector::Text("Click me".to_string());
1963 assert!(text.to_count_query().contains(".length"));
1964
1965 let testid = Selector::TestId("btn".to_string());
1967 assert!(testid.to_count_query().contains("data-testid"));
1968
1969 let entity = Selector::Entity("hero".to_string());
1971 assert!(entity.to_count_query().contains("__wasm_count_entities"));
1972
1973 let css_text = Selector::CssWithText {
1975 css: "button".to_string(),
1976 text: "Submit".to_string(),
1977 };
1978 assert!(css_text.to_count_query().contains(".length"));
1979
1980 let canvas = Selector::CanvasEntity {
1982 entity: "player".to_string(),
1983 };
1984 assert!(canvas
1985 .to_count_query()
1986 .contains("__wasm_count_canvas_entities"));
1987 }
1988 }
1989
1990 mod additional_bounding_box_tests {
1991 use super::*;
1992
1993 #[test]
1994 fn test_bounding_box_creation_and_fields() {
1995 let bbox = BoundingBox::new(10.0, 20.0, 100.0, 50.0);
1996 assert!((bbox.x - 10.0).abs() < f32::EPSILON);
1997 assert!((bbox.y - 20.0).abs() < f32::EPSILON);
1998 assert!((bbox.width - 100.0).abs() < f32::EPSILON);
1999 assert!((bbox.height - 50.0).abs() < f32::EPSILON);
2000 }
2001
2002 #[test]
2003 fn test_bounding_box_contains_edge_cases() {
2004 let bbox = BoundingBox::new(0.0, 0.0, 100.0, 100.0);
2005 assert!(bbox.contains(&Point::new(0.0, 0.0)));
2007 assert!(bbox.contains(&Point::new(100.0, 100.0)));
2008 assert!(!bbox.contains(&Point::new(-1.0, 50.0)));
2010 assert!(!bbox.contains(&Point::new(101.0, 50.0)));
2011 }
2012 }
2013
2014 #[allow(clippy::uninlined_format_args, unused_imports)]
2020 mod qa_checklist_locator_tests {
2021 #[allow(unused_imports)]
2022 use super::*;
2023
2024 #[test]
2026 fn test_long_selector_limit() {
2027 const MAX_SELECTOR_LENGTH: usize = 10 * 1024; let long_selector = "a".repeat(MAX_SELECTOR_LENGTH + 1);
2029
2030 let is_too_long = long_selector.len() > MAX_SELECTOR_LENGTH;
2032 assert!(is_too_long, "Should detect selector exceeding 10KB limit");
2033
2034 let truncated = if long_selector.len() > MAX_SELECTOR_LENGTH {
2036 &long_selector[..MAX_SELECTOR_LENGTH]
2037 } else {
2038 &long_selector
2039 };
2040 assert_eq!(truncated.len(), MAX_SELECTOR_LENGTH);
2041 }
2042
2043 #[test]
2045 fn test_shadow_dom_selector_support() {
2046 let shadow_selector = "host-element >>> .inner-element";
2048
2049 let has_shadow_combinator =
2051 shadow_selector.contains(">>>") || shadow_selector.contains("/deep/");
2052 assert!(has_shadow_combinator, "Shadow DOM combinator recognized");
2053
2054 let query = if shadow_selector.contains(">>>") {
2056 let parts: Vec<&str> = shadow_selector.split(">>>").collect();
2057 format!(
2058 "document.querySelector('{}').shadowRoot.querySelector('{}')",
2059 parts[0].trim(),
2060 parts.get(1).unwrap_or(&"").trim()
2061 )
2062 } else {
2063 shadow_selector.to_string()
2064 };
2065 assert!(query.contains("shadowRoot"), "Shadow DOM query generated");
2066 }
2067
2068 #[test]
2070 fn test_iframe_context_switching() {
2071 let iframe_selector = "iframe#game-frame";
2073 let inner_selector = "button.start";
2074
2075 let query = format!(
2077 "document.querySelector('{}').contentDocument.querySelector('{}')",
2078 iframe_selector, inner_selector
2079 );
2080
2081 assert!(query.contains("contentDocument"), "iframe context switch");
2082 assert!(query.contains(inner_selector), "Inner selector preserved");
2083 }
2084
2085 #[test]
2087 fn test_empty_selector_rejection() {
2088 let empty_selector = "";
2089 let whitespace_selector = " ";
2090
2091 let is_empty_or_whitespace =
2092 empty_selector.is_empty() || whitespace_selector.trim().is_empty();
2093 assert!(
2094 is_empty_or_whitespace,
2095 "Empty/whitespace selectors detected"
2096 );
2097 }
2098
2099 #[test]
2101 fn test_special_char_selector_escaping() {
2102 let selector_with_quotes = r#"button[data-name="test's"]"#;
2103 let selector_with_brackets = "div[class~=foo\\[bar\\]]";
2104
2105 assert!(selector_with_quotes.contains('"'));
2107 assert!(selector_with_brackets.contains('['));
2108 }
2109 }
2110
2111 mod semantic_locator_tests {
2116 use super::*;
2117
2118 #[test]
2119 fn test_role_selector_query() {
2120 let selector = Selector::role("button");
2121 let query = selector.to_query();
2122 assert!(query.contains("role"));
2123 assert!(query.contains("button"));
2124 }
2125
2126 #[test]
2127 fn test_role_selector_with_name() {
2128 let selector = Selector::role_with_name("button", "Submit");
2129 let query = selector.to_query();
2130 assert!(query.contains("role"));
2131 assert!(query.contains("Submit"));
2132 }
2133
2134 #[test]
2135 fn test_role_selector_count_query() {
2136 let selector = Selector::role("textbox");
2137 let query = selector.to_count_query();
2138 assert!(query.contains("role"));
2139 assert!(query.contains(".length"));
2140 }
2141
2142 #[test]
2143 fn test_label_selector_query() {
2144 let selector = Selector::label("Username");
2145 let query = selector.to_query();
2146 assert!(query.contains("label"));
2147 assert!(query.contains("Username"));
2148 }
2149
2150 #[test]
2151 fn test_label_selector_count_query() {
2152 let selector = Selector::label("Email");
2153 let query = selector.to_count_query();
2154 assert!(query.contains("label"));
2155 assert!(query.contains(".length"));
2156 }
2157
2158 #[test]
2159 fn test_placeholder_selector_query() {
2160 let selector = Selector::placeholder("Enter email");
2161 let query = selector.to_query();
2162 assert!(query.contains("placeholder"));
2163 assert!(query.contains("Enter email"));
2164 }
2165
2166 #[test]
2167 fn test_placeholder_selector_count_query() {
2168 let selector = Selector::placeholder("Search");
2169 let query = selector.to_count_query();
2170 assert!(query.contains("placeholder"));
2171 assert!(query.contains(".length"));
2172 }
2173
2174 #[test]
2175 fn test_alt_text_selector_query() {
2176 let selector = Selector::alt_text("Company Logo");
2177 let query = selector.to_query();
2178 assert!(query.contains("alt"));
2179 assert!(query.contains("Company Logo"));
2180 }
2181
2182 #[test]
2183 fn test_alt_text_selector_count_query() {
2184 let selector = Selector::alt_text("Logo");
2185 let query = selector.to_count_query();
2186 assert!(query.contains("alt"));
2187 assert!(query.contains(".length"));
2188 }
2189
2190 #[test]
2191 fn test_locator_by_role() {
2192 let locator = Locator::by_role("button");
2193 assert!(matches!(locator.selector(), Selector::Role { .. }));
2194 }
2195
2196 #[test]
2197 fn test_locator_by_role_with_name() {
2198 let locator = Locator::by_role_with_name("link", "Home");
2199 match locator.selector() {
2200 Selector::Role { name, .. } => assert!(name.is_some()),
2201 _ => panic!("Expected Role selector"),
2202 }
2203 }
2204
2205 #[test]
2206 fn test_locator_by_label() {
2207 let locator = Locator::by_label("Password");
2208 assert!(matches!(locator.selector(), Selector::Label(_)));
2209 }
2210
2211 #[test]
2212 fn test_locator_by_placeholder() {
2213 let locator = Locator::by_placeholder("Enter your name");
2214 assert!(matches!(locator.selector(), Selector::Placeholder(_)));
2215 }
2216
2217 #[test]
2218 fn test_locator_by_alt_text() {
2219 let locator = Locator::by_alt_text("Profile Picture");
2220 assert!(matches!(locator.selector(), Selector::AltText(_)));
2221 }
2222
2223 #[test]
2224 fn test_locator_by_test_id() {
2225 let locator = Locator::by_test_id("submit-btn");
2226 assert!(matches!(locator.selector(), Selector::TestId(_)));
2227 }
2228
2229 #[test]
2230 fn test_locator_by_text() {
2231 let locator = Locator::by_text("Click here");
2232 assert!(matches!(locator.selector(), Selector::Text(_)));
2233 }
2234 }
2235
2236 mod locator_operations_tests {
2241 use super::*;
2242
2243 #[test]
2244 fn test_filter_with_has_text() {
2245 let locator = Locator::new("button").filter(FilterOptions::new().has_text("Submit"));
2246 assert!(matches!(locator.selector(), Selector::CssWithText { .. }));
2247 }
2248
2249 #[test]
2250 fn test_filter_options_builder() {
2251 let options = FilterOptions::new()
2252 .has_text("Hello")
2253 .has_not_text("Goodbye");
2254 assert!(options.has_text.is_some());
2255 assert!(options.has_not_text.is_some());
2256 }
2257
2258 #[test]
2259 fn test_filter_options_has() {
2260 let child = Locator::new(".child");
2261 let options = FilterOptions::new().has(child);
2262 assert!(options.has.is_some());
2263 }
2264
2265 #[test]
2266 fn test_filter_options_has_not() {
2267 let child = Locator::new(".excluded");
2268 let options = FilterOptions::new().has_not(child);
2269 assert!(options.has_not.is_some());
2270 }
2271
2272 #[test]
2273 fn test_locator_and() {
2274 let locator1 = Locator::new("div");
2275 let locator2 = Locator::new(".active");
2276 let combined = locator1.and(locator2);
2277 if let Selector::Css(s) = combined.selector() {
2278 assert!(s.contains("div"));
2279 assert!(s.contains(".active"));
2280 } else {
2281 panic!("Expected CSS selector");
2282 }
2283 }
2284
2285 #[test]
2286 fn test_locator_or() {
2287 let locator1 = Locator::new("button");
2288 let locator2 = Locator::new("a.btn");
2289 let combined = locator1.or(locator2);
2290 if let Selector::Css(s) = combined.selector() {
2291 assert!(s.contains("button"));
2292 assert!(s.contains("a.btn"));
2293 assert!(s.contains(", "));
2294 } else {
2295 panic!("Expected CSS selector");
2296 }
2297 }
2298
2299 #[test]
2300 fn test_locator_first() {
2301 let locator = Locator::new("li").first();
2302 if let Selector::Css(s) = locator.selector() {
2303 assert!(s.contains(":first-child"));
2304 } else {
2305 panic!("Expected CSS selector");
2306 }
2307 }
2308
2309 #[test]
2310 fn test_locator_last() {
2311 let locator = Locator::new("li").last();
2312 if let Selector::Css(s) = locator.selector() {
2313 assert!(s.contains(":last-child"));
2314 } else {
2315 panic!("Expected CSS selector");
2316 }
2317 }
2318
2319 #[test]
2320 fn test_locator_nth() {
2321 let locator = Locator::new("li").nth(2);
2322 if let Selector::Css(s) = locator.selector() {
2323 assert!(s.contains(":nth-child(3)")); } else {
2325 panic!("Expected CSS selector");
2326 }
2327 }
2328
2329 #[test]
2330 fn test_locator_and_non_css() {
2331 let locator1 = Locator::from_selector(Selector::Entity("hero".to_string()));
2332 let locator2 = Locator::new("div");
2333 let combined = locator1.and(locator2);
2334 assert!(matches!(combined.selector(), Selector::Entity(_)));
2336 }
2337 }
2338
2339 mod mouse_actions_tests {
2344 use super::*;
2345
2346 #[test]
2347 fn test_right_click() {
2348 let locator = Locator::new("button");
2349 let action = locator.right_click().unwrap();
2350 assert!(matches!(action, LocatorAction::RightClick { .. }));
2351 }
2352
2353 #[test]
2354 fn test_hover() {
2355 let locator = Locator::new("menu-item");
2356 let action = locator.hover().unwrap();
2357 assert!(matches!(action, LocatorAction::Hover { .. }));
2358 }
2359
2360 #[test]
2361 fn test_focus() {
2362 let locator = Locator::new("input");
2363 let action = locator.focus().unwrap();
2364 assert!(matches!(action, LocatorAction::Focus { .. }));
2365 }
2366
2367 #[test]
2368 fn test_blur() {
2369 let locator = Locator::new("input");
2370 let action = locator.blur().unwrap();
2371 assert!(matches!(action, LocatorAction::Blur { .. }));
2372 }
2373
2374 #[test]
2375 fn test_check() {
2376 let locator = Locator::new("input[type=checkbox]");
2377 let action = locator.check().unwrap();
2378 assert!(matches!(action, LocatorAction::Check { .. }));
2379 }
2380
2381 #[test]
2382 fn test_uncheck() {
2383 let locator = Locator::new("input[type=checkbox]");
2384 let action = locator.uncheck().unwrap();
2385 assert!(matches!(action, LocatorAction::Uncheck { .. }));
2386 }
2387
2388 #[test]
2389 fn test_scroll_into_view() {
2390 let locator = Locator::new("footer");
2391 let action = locator.scroll_into_view().unwrap();
2392 assert!(matches!(action, LocatorAction::ScrollIntoView { .. }));
2393 }
2394
2395 #[test]
2396 fn test_click_with_options_default() {
2397 let options = ClickOptions::new();
2398 assert_eq!(options.button, MouseButton::Left);
2399 assert_eq!(options.click_count, 1);
2400 assert!(options.position.is_none());
2401 assert!(options.modifiers.is_empty());
2402 }
2403
2404 #[test]
2405 fn test_click_with_options_right_button() {
2406 let options = ClickOptions::new().button(MouseButton::Right);
2407 assert_eq!(options.button, MouseButton::Right);
2408 }
2409
2410 #[test]
2411 fn test_click_with_options_double_click() {
2412 let options = ClickOptions::new().click_count(2);
2413 assert_eq!(options.click_count, 2);
2414 }
2415
2416 #[test]
2417 fn test_click_with_options_position() {
2418 let options = ClickOptions::new().position(Point::new(10.0, 20.0));
2419 assert!(options.position.is_some());
2420 }
2421
2422 #[test]
2423 fn test_click_with_options_modifier() {
2424 let options = ClickOptions::new()
2425 .modifier(KeyModifier::Shift)
2426 .modifier(KeyModifier::Control);
2427 assert_eq!(options.modifiers.len(), 2);
2428 }
2429
2430 #[test]
2431 fn test_click_with_custom_options() {
2432 let locator = Locator::new("button");
2433 let options = ClickOptions::new().button(MouseButton::Middle);
2434 let action = locator.click_with_options(options).unwrap();
2435 assert!(matches!(action, LocatorAction::ClickWithOptions { .. }));
2436 }
2437
2438 #[test]
2439 fn test_mouse_button_default() {
2440 let button: MouseButton = Default::default();
2441 assert_eq!(button, MouseButton::Left);
2442 }
2443
2444 #[test]
2445 fn test_locator_action_locator_accessor_all_variants() {
2446 let locator = Locator::new("button");
2447
2448 let _ = locator.right_click().unwrap().locator();
2450 let _ = locator.hover().unwrap().locator();
2451 let _ = locator.focus().unwrap().locator();
2452 let _ = locator.blur().unwrap().locator();
2453 let _ = locator.check().unwrap().locator();
2454 let _ = locator.uncheck().unwrap().locator();
2455 let _ = locator.scroll_into_view().unwrap().locator();
2456 let _ = locator
2457 .click_with_options(ClickOptions::new())
2458 .unwrap()
2459 .locator();
2460 }
2461 }
2462
2463 mod element_state_assertions_tests {
2468 use super::*;
2469
2470 #[test]
2471 fn test_to_be_enabled() {
2472 let locator = Locator::new("button");
2473 let assertion = expect(locator).to_be_enabled();
2474 assert!(matches!(assertion, ExpectAssertion::IsEnabled { .. }));
2475 }
2476
2477 #[test]
2478 fn test_to_be_disabled() {
2479 let locator = Locator::new("button");
2480 let assertion = expect(locator).to_be_disabled();
2481 assert!(matches!(assertion, ExpectAssertion::IsDisabled { .. }));
2482 }
2483
2484 #[test]
2485 fn test_to_be_checked() {
2486 let locator = Locator::new("input[type=checkbox]");
2487 let assertion = expect(locator).to_be_checked();
2488 assert!(matches!(assertion, ExpectAssertion::IsChecked { .. }));
2489 }
2490
2491 #[test]
2492 fn test_to_be_editable() {
2493 let locator = Locator::new("textarea");
2494 let assertion = expect(locator).to_be_editable();
2495 assert!(matches!(assertion, ExpectAssertion::IsEditable { .. }));
2496 }
2497
2498 #[test]
2499 fn test_to_be_focused() {
2500 let locator = Locator::new("input");
2501 let assertion = expect(locator).to_be_focused();
2502 assert!(matches!(assertion, ExpectAssertion::IsFocused { .. }));
2503 }
2504
2505 #[test]
2506 fn test_to_be_empty() {
2507 let locator = Locator::new("div");
2508 let assertion = expect(locator).to_be_empty();
2509 assert!(matches!(assertion, ExpectAssertion::IsEmpty { .. }));
2510 }
2511
2512 #[test]
2513 fn test_to_have_value() {
2514 let locator = Locator::new("input");
2515 let assertion = expect(locator).to_have_value("test");
2516 assert!(matches!(assertion, ExpectAssertion::HasValue { .. }));
2517 }
2518
2519 #[test]
2520 fn test_to_have_css() {
2521 let locator = Locator::new("div");
2522 let assertion = expect(locator).to_have_css("color", "red");
2523 assert!(matches!(assertion, ExpectAssertion::HasCss { .. }));
2524 }
2525
2526 #[test]
2527 fn test_to_have_class() {
2528 let locator = Locator::new("div");
2529 let assertion = expect(locator).to_have_class("active");
2530 assert!(matches!(assertion, ExpectAssertion::HasClass { .. }));
2531 }
2532
2533 #[test]
2534 fn test_to_have_id() {
2535 let locator = Locator::new("div");
2536 let assertion = expect(locator).to_have_id("main-content");
2537 assert!(matches!(assertion, ExpectAssertion::HasId { .. }));
2538 }
2539
2540 #[test]
2541 fn test_to_have_attribute() {
2542 let locator = Locator::new("input");
2543 let assertion = expect(locator).to_have_attribute("type", "text");
2544 assert!(matches!(assertion, ExpectAssertion::HasAttribute { .. }));
2545 }
2546
2547 #[test]
2548 fn test_validate_has_value_pass() {
2549 let locator = Locator::new("input");
2550 let assertion = expect(locator).to_have_value("test123");
2551 assert!(assertion.validate("test123").is_ok());
2552 }
2553
2554 #[test]
2555 fn test_validate_has_value_fail() {
2556 let locator = Locator::new("input");
2557 let assertion = expect(locator).to_have_value("expected");
2558 assert!(assertion.validate("actual").is_err());
2559 }
2560
2561 #[test]
2562 fn test_validate_has_class_pass() {
2563 let locator = Locator::new("div");
2564 let assertion = expect(locator).to_have_class("active");
2565 assert!(assertion.validate("btn active primary").is_ok());
2566 }
2567
2568 #[test]
2569 fn test_validate_has_class_fail() {
2570 let locator = Locator::new("div");
2571 let assertion = expect(locator).to_have_class("missing");
2572 assert!(assertion.validate("btn active").is_err());
2573 }
2574
2575 #[test]
2576 fn test_validate_has_id_pass() {
2577 let locator = Locator::new("div");
2578 let assertion = expect(locator).to_have_id("main");
2579 assert!(assertion.validate("main").is_ok());
2580 }
2581
2582 #[test]
2583 fn test_validate_has_attribute_pass() {
2584 let locator = Locator::new("input");
2585 let assertion = expect(locator).to_have_attribute("type", "text");
2586 assert!(assertion.validate("text").is_ok());
2587 }
2588
2589 #[test]
2590 fn test_validate_state_enabled_pass() {
2591 let locator = Locator::new("button");
2592 let assertion = expect(locator).to_be_enabled();
2593 assert!(assertion.validate_state(true).is_ok());
2594 }
2595
2596 #[test]
2597 fn test_validate_state_enabled_fail() {
2598 let locator = Locator::new("button");
2599 let assertion = expect(locator).to_be_enabled();
2600 assert!(assertion.validate_state(false).is_err());
2601 }
2602
2603 #[test]
2604 fn test_validate_state_disabled_pass() {
2605 let locator = Locator::new("button");
2606 let assertion = expect(locator).to_be_disabled();
2607 assert!(assertion.validate_state(true).is_ok());
2608 }
2609
2610 #[test]
2611 fn test_validate_state_checked_pass() {
2612 let locator = Locator::new("input");
2613 let assertion = expect(locator).to_be_checked();
2614 assert!(assertion.validate_state(true).is_ok());
2615 }
2616
2617 #[test]
2618 fn test_validate_state_editable_pass() {
2619 let locator = Locator::new("textarea");
2620 let assertion = expect(locator).to_be_editable();
2621 assert!(assertion.validate_state(true).is_ok());
2622 }
2623
2624 #[test]
2625 fn test_validate_state_focused_pass() {
2626 let locator = Locator::new("input");
2627 let assertion = expect(locator).to_be_focused();
2628 assert!(assertion.validate_state(true).is_ok());
2629 }
2630
2631 #[test]
2632 fn test_validate_state_empty_pass() {
2633 let locator = Locator::new("div");
2634 let assertion = expect(locator).to_be_empty();
2635 assert!(assertion.validate_state(true).is_ok());
2636 }
2637
2638 #[test]
2639 fn test_validate_state_visible_pass() {
2640 let locator = Locator::new("div");
2641 let assertion = expect(locator).to_be_visible();
2642 assert!(assertion.validate_state(true).is_ok());
2643 }
2644
2645 #[test]
2646 fn test_validate_state_hidden_pass() {
2647 let locator = Locator::new("div");
2648 let assertion = expect(locator).to_be_hidden();
2649 assert!(assertion.validate_state(true).is_ok());
2650 }
2651 }
2652
2653 mod h0_auto_waiting_tests {
2658 use super::*;
2659
2660 #[test]
2661 fn h0_locator_01_default_timeout_is_5_seconds() {
2662 assert_eq!(DEFAULT_TIMEOUT_MS, 5000);
2663 }
2664
2665 #[test]
2666 fn h0_locator_02_default_poll_interval_is_50ms() {
2667 assert_eq!(DEFAULT_POLL_INTERVAL_MS, 50);
2668 }
2669
2670 #[test]
2671 fn h0_locator_03_locator_options_default_timeout() {
2672 let opts = LocatorOptions::default();
2673 assert_eq!(opts.timeout, Duration::from_millis(DEFAULT_TIMEOUT_MS));
2674 }
2675
2676 #[test]
2677 fn h0_locator_04_locator_options_default_strict_true() {
2678 let opts = LocatorOptions::default();
2679 assert!(opts.strict);
2680 }
2681
2682 #[test]
2683 fn h0_locator_05_locator_options_default_visible_true() {
2684 let opts = LocatorOptions::default();
2685 assert!(opts.visible);
2686 }
2687
2688 #[test]
2689 fn h0_locator_06_with_timeout_custom_value() {
2690 let locator = Locator::new("button").with_timeout(Duration::from_secs(30));
2691 assert_eq!(locator.options().timeout, Duration::from_secs(30));
2692 }
2693
2694 #[test]
2695 fn h0_locator_07_with_strict_false() {
2696 let locator = Locator::new("button").with_strict(false);
2697 assert!(!locator.options().strict);
2698 }
2699
2700 #[test]
2701 fn h0_locator_08_with_visible_false() {
2702 let locator = Locator::new("button").with_visible(false);
2703 assert!(!locator.options().visible);
2704 }
2705
2706 #[test]
2707 fn h0_locator_09_wait_for_visible_action() {
2708 let locator = Locator::new("button");
2709 let action = locator.wait_for_visible().unwrap();
2710 assert!(matches!(action, LocatorAction::WaitForVisible { .. }));
2711 }
2712
2713 #[test]
2714 fn h0_locator_10_wait_for_hidden_action() {
2715 let locator = Locator::new("button");
2716 let action = locator.wait_for_hidden().unwrap();
2717 assert!(matches!(action, LocatorAction::WaitForHidden { .. }));
2718 }
2719 }
2720
2721 mod h0_semantic_locator_tests {
2726 use super::*;
2727
2728 #[test]
2729 fn h0_locator_11_role_selector_button() {
2730 let selector = Selector::role("button");
2731 assert!(matches!(selector, Selector::Role { role, name: None } if role == "button"));
2732 }
2733
2734 #[test]
2735 fn h0_locator_12_role_selector_with_name() {
2736 let selector = Selector::role_with_name("button", "Submit");
2737 assert!(
2738 matches!(selector, Selector::Role { role, name: Some(n) } if role == "button" && n == "Submit")
2739 );
2740 }
2741
2742 #[test]
2743 fn h0_locator_13_label_selector() {
2744 let selector = Selector::label("Username");
2745 assert!(matches!(selector, Selector::Label(l) if l == "Username"));
2746 }
2747
2748 #[test]
2749 fn h0_locator_14_placeholder_selector() {
2750 let selector = Selector::placeholder("Enter email");
2751 assert!(matches!(selector, Selector::Placeholder(p) if p == "Enter email"));
2752 }
2753
2754 #[test]
2755 fn h0_locator_15_alt_text_selector() {
2756 let selector = Selector::alt_text("Logo image");
2757 assert!(matches!(selector, Selector::AltText(a) if a == "Logo image"));
2758 }
2759
2760 #[test]
2761 fn h0_locator_16_role_to_query() {
2762 let selector = Selector::role("button");
2763 let query = selector.to_query();
2764 assert!(query.contains("role") || query.contains("button"));
2765 }
2766
2767 #[test]
2768 fn h0_locator_17_label_to_query() {
2769 let selector = Selector::label("Email");
2770 let query = selector.to_query();
2771 assert!(query.contains("label") || query.contains("Email"));
2772 }
2773
2774 #[test]
2775 fn h0_locator_18_placeholder_to_query() {
2776 let selector = Selector::placeholder("Search");
2777 let query = selector.to_query();
2778 assert!(query.contains("placeholder") || query.contains("Search"));
2779 }
2780
2781 #[test]
2782 fn h0_locator_19_alt_text_to_query() {
2783 let selector = Selector::alt_text("Company Logo");
2784 let query = selector.to_query();
2785 assert!(query.contains("alt") || query.contains("Company Logo"));
2786 }
2787
2788 #[test]
2789 fn h0_locator_20_css_selector_factory() {
2790 let selector = Selector::css("div.container");
2791 assert!(matches!(selector, Selector::Css(s) if s == "div.container"));
2792 }
2793 }
2794
2795 mod h0_expect_assertion_tests {
2800 use super::*;
2801
2802 #[test]
2803 fn h0_locator_21_expect_to_have_text() {
2804 let locator = Locator::new("span");
2805 let assertion = expect(locator).to_have_text("Hello");
2806 assert!(matches!(assertion, ExpectAssertion::HasText { .. }));
2807 }
2808
2809 #[test]
2810 fn h0_locator_22_expect_to_contain_text() {
2811 let locator = Locator::new("span");
2812 let assertion = expect(locator).to_contain_text("ell");
2813 assert!(matches!(assertion, ExpectAssertion::ContainsText { .. }));
2814 }
2815
2816 #[test]
2817 fn h0_locator_23_expect_to_have_count() {
2818 let locator = Locator::new("li");
2819 let assertion = expect(locator).to_have_count(5);
2820 assert!(
2821 matches!(assertion, ExpectAssertion::HasCount { expected, .. } if expected == 5)
2822 );
2823 }
2824
2825 #[test]
2826 fn h0_locator_24_expect_to_be_visible() {
2827 let locator = Locator::new("button");
2828 let assertion = expect(locator).to_be_visible();
2829 assert!(matches!(assertion, ExpectAssertion::IsVisible { .. }));
2830 }
2831
2832 #[test]
2833 fn h0_locator_25_expect_to_be_hidden() {
2834 let locator = Locator::new("button");
2835 let assertion = expect(locator).to_be_hidden();
2836 assert!(matches!(assertion, ExpectAssertion::IsHidden { .. }));
2837 }
2838
2839 #[test]
2840 fn h0_locator_26_expect_to_be_enabled() {
2841 let locator = Locator::new("button");
2842 let assertion = expect(locator).to_be_enabled();
2843 assert!(matches!(assertion, ExpectAssertion::IsEnabled { .. }));
2844 }
2845
2846 #[test]
2847 fn h0_locator_27_expect_to_be_disabled() {
2848 let locator = Locator::new("button");
2849 let assertion = expect(locator).to_be_disabled();
2850 assert!(matches!(assertion, ExpectAssertion::IsDisabled { .. }));
2851 }
2852
2853 #[test]
2854 fn h0_locator_28_expect_to_be_checked() {
2855 let locator = Locator::new("input[type=checkbox]");
2856 let assertion = expect(locator).to_be_checked();
2857 assert!(matches!(assertion, ExpectAssertion::IsChecked { .. }));
2858 }
2859
2860 #[test]
2861 fn h0_locator_29_expect_to_have_value() {
2862 let locator = Locator::new("input");
2863 let assertion = expect(locator).to_have_value("test");
2864 assert!(matches!(assertion, ExpectAssertion::HasValue { .. }));
2865 }
2866
2867 #[test]
2868 fn h0_locator_30_expect_to_have_attribute() {
2869 let locator = Locator::new("input");
2870 let assertion = expect(locator).to_have_attribute("type", "email");
2871 assert!(matches!(assertion, ExpectAssertion::HasAttribute { .. }));
2872 }
2873 }
2874
2875 mod h0_locator_action_tests {
2880 use super::*;
2881
2882 #[test]
2883 fn h0_locator_31_click_action() {
2884 let locator = Locator::new("button");
2885 let action = locator.click().unwrap();
2886 assert!(matches!(action, LocatorAction::Click { .. }));
2887 }
2888
2889 #[test]
2890 fn h0_locator_32_double_click_action() {
2891 let locator = Locator::new("button");
2892 let action = locator.double_click().unwrap();
2893 assert!(matches!(action, LocatorAction::DoubleClick { .. }));
2894 }
2895
2896 #[test]
2897 fn h0_locator_33_fill_action() {
2898 let locator = Locator::new("input");
2899 let action = locator.fill("hello").unwrap();
2900 assert!(matches!(action, LocatorAction::Fill { text, .. } if text == "hello"));
2901 }
2902
2903 #[test]
2904 fn h0_locator_34_hover_action() {
2905 let locator = Locator::new("button");
2906 let action = locator.hover().unwrap();
2907 assert!(matches!(action, LocatorAction::Hover { .. }));
2908 }
2909
2910 #[test]
2911 fn h0_locator_35_focus_action() {
2912 let locator = Locator::new("input");
2913 let action = locator.focus().unwrap();
2914 assert!(matches!(action, LocatorAction::Focus { .. }));
2915 }
2916
2917 #[test]
2918 fn h0_locator_36_drag_to_action() {
2919 let locator = Locator::new("div.draggable");
2920 let action = locator.drag_to(&Point::new(100.0, 200.0)).build();
2921 assert!(matches!(action, LocatorAction::Drag { .. }));
2922 }
2923
2924 #[test]
2925 fn h0_locator_37_drag_steps_custom() {
2926 let locator = Locator::new("div");
2927 let action = locator.drag_to(&Point::new(0.0, 0.0)).steps(25).build();
2928 assert!(matches!(action, LocatorAction::Drag { steps: 25, .. }));
2929 }
2930
2931 #[test]
2932 fn h0_locator_38_drag_duration_custom() {
2933 let locator = Locator::new("div");
2934 let action = locator
2935 .drag_to(&Point::new(0.0, 0.0))
2936 .duration(Duration::from_secs(2))
2937 .build();
2938 assert!(
2939 matches!(action, LocatorAction::Drag { duration, .. } if duration == Duration::from_secs(2))
2940 );
2941 }
2942
2943 #[test]
2944 fn h0_locator_39_text_content_query() {
2945 let locator = Locator::new("span");
2946 let query = locator.text_content().unwrap();
2947 assert!(matches!(query, LocatorQuery::TextContent { .. }));
2948 }
2949
2950 #[test]
2951 fn h0_locator_40_count_query() {
2952 let locator = Locator::new("li");
2953 let query = locator.count().unwrap();
2954 assert!(matches!(query, LocatorQuery::Count { .. }));
2955 }
2956 }
2957
2958 mod h0_geometry_tests {
2963 use super::*;
2964
2965 #[test]
2966 fn h0_locator_41_point_new() {
2967 let p = Point::new(10.5, 20.5);
2968 assert!((p.x - 10.5).abs() < f32::EPSILON);
2969 assert!((p.y - 20.5).abs() < f32::EPSILON);
2970 }
2971
2972 #[test]
2973 fn h0_locator_42_bounding_box_new() {
2974 let bbox = BoundingBox::new(10.0, 20.0, 100.0, 50.0);
2975 assert!((bbox.x - 10.0).abs() < f32::EPSILON);
2976 assert!((bbox.width - 100.0).abs() < f32::EPSILON);
2977 }
2978
2979 #[test]
2980 fn h0_locator_43_bounding_box_center() {
2981 let bbox = BoundingBox::new(0.0, 0.0, 100.0, 100.0);
2982 let center = bbox.center();
2983 assert!((center.x - 50.0).abs() < f32::EPSILON);
2984 assert!((center.y - 50.0).abs() < f32::EPSILON);
2985 }
2986
2987 #[test]
2988 fn h0_locator_44_bounding_box_contains_inside() {
2989 let bbox = BoundingBox::new(0.0, 0.0, 100.0, 100.0);
2990 assert!(bbox.contains(&Point::new(50.0, 50.0)));
2991 }
2992
2993 #[test]
2994 fn h0_locator_45_bounding_box_contains_outside() {
2995 let bbox = BoundingBox::new(0.0, 0.0, 100.0, 100.0);
2996 assert!(!bbox.contains(&Point::new(150.0, 150.0)));
2997 }
2998
2999 #[test]
3000 fn h0_locator_46_bounding_box_contains_edge() {
3001 let bbox = BoundingBox::new(0.0, 0.0, 100.0, 100.0);
3002 assert!(bbox.contains(&Point::new(0.0, 0.0)));
3003 }
3004
3005 #[test]
3006 fn h0_locator_47_drag_operation_default_steps() {
3007 let drag = DragOperation::to(Point::new(100.0, 100.0));
3008 assert_eq!(drag.steps, 10);
3009 }
3010
3011 #[test]
3012 fn h0_locator_48_drag_operation_default_duration() {
3013 let drag = DragOperation::to(Point::new(100.0, 100.0));
3014 assert_eq!(drag.duration, Duration::from_millis(500));
3015 }
3016
3017 #[test]
3018 fn h0_locator_49_locator_bounding_box_query() {
3019 let locator = Locator::new("div");
3020 let query = locator.bounding_box().unwrap();
3021 assert!(matches!(query, LocatorQuery::BoundingBox { .. }));
3022 }
3023
3024 #[test]
3025 fn h0_locator_50_locator_is_visible_query() {
3026 let locator = Locator::new("div");
3027 let query = locator.is_visible().unwrap();
3028 assert!(matches!(query, LocatorQuery::IsVisible { .. }));
3029 }
3030 }
3031
3032 mod coverage_edge_cases {
3037 use super::*;
3038
3039 #[test]
3044 fn test_validate_state_disabled_fail() {
3045 let locator = Locator::new("button");
3046 let assertion = expect(locator).to_be_disabled();
3047 let result = assertion.validate_state(false);
3048 assert!(result.is_err());
3049 let err = result.unwrap_err();
3050 assert!(err.to_string().contains("disabled"));
3051 }
3052
3053 #[test]
3054 fn test_validate_state_checked_fail() {
3055 let locator = Locator::new("input");
3056 let assertion = expect(locator).to_be_checked();
3057 let result = assertion.validate_state(false);
3058 assert!(result.is_err());
3059 let err = result.unwrap_err();
3060 assert!(err.to_string().contains("checked"));
3061 }
3062
3063 #[test]
3064 fn test_validate_state_editable_fail() {
3065 let locator = Locator::new("textarea");
3066 let assertion = expect(locator).to_be_editable();
3067 let result = assertion.validate_state(false);
3068 assert!(result.is_err());
3069 let err = result.unwrap_err();
3070 assert!(err.to_string().contains("editable"));
3071 }
3072
3073 #[test]
3074 fn test_validate_state_focused_fail() {
3075 let locator = Locator::new("input");
3076 let assertion = expect(locator).to_be_focused();
3077 let result = assertion.validate_state(false);
3078 assert!(result.is_err());
3079 let err = result.unwrap_err();
3080 assert!(err.to_string().contains("focused"));
3081 }
3082
3083 #[test]
3084 fn test_validate_state_empty_fail() {
3085 let locator = Locator::new("div");
3086 let assertion = expect(locator).to_be_empty();
3087 let result = assertion.validate_state(false);
3088 assert!(result.is_err());
3089 let err = result.unwrap_err();
3090 assert!(err.to_string().contains("empty"));
3091 }
3092
3093 #[test]
3094 fn test_validate_state_visible_fail() {
3095 let locator = Locator::new("div");
3096 let assertion = expect(locator).to_be_visible();
3097 let result = assertion.validate_state(false);
3098 assert!(result.is_err());
3099 let err = result.unwrap_err();
3100 assert!(err.to_string().contains("visible"));
3101 }
3102
3103 #[test]
3104 fn test_validate_state_hidden_fail() {
3105 let locator = Locator::new("div");
3106 let assertion = expect(locator).to_be_hidden();
3107 let result = assertion.validate_state(false);
3108 assert!(result.is_err());
3109 let err = result.unwrap_err();
3110 assert!(err.to_string().contains("hidden"));
3111 }
3112
3113 #[test]
3118 fn test_validate_state_non_state_assertion() {
3119 let locator = Locator::new("span");
3120 let assertion = expect(locator).to_have_text("test");
3121 assert!(assertion.validate_state(true).is_ok());
3123 assert!(assertion.validate_state(false).is_ok());
3124 }
3125
3126 #[test]
3131 fn test_validate_count_non_count_assertion() {
3132 let locator = Locator::new("span");
3133 let assertion = expect(locator).to_have_text("test");
3134 assert!(assertion.validate_count(0).is_ok());
3136 assert!(assertion.validate_count(100).is_ok());
3137 }
3138
3139 #[test]
3144 fn test_validate_contains_text_fail() {
3145 let locator = Locator::new("span");
3146 let assertion = expect(locator).to_contain_text("needle");
3147 let result = assertion.validate("haystack without the word");
3148 assert!(result.is_err());
3149 let err = result.unwrap_err();
3150 assert!(err.to_string().contains("needle"));
3151 }
3152
3153 #[test]
3158 fn test_validate_has_id_fail() {
3159 let locator = Locator::new("div");
3160 let assertion = expect(locator).to_have_id("expected-id");
3161 let result = assertion.validate("actual-id");
3162 assert!(result.is_err());
3163 let err = result.unwrap_err();
3164 assert!(err.to_string().contains("expected-id"));
3165 }
3166
3167 #[test]
3172 fn test_validate_has_attribute_fail() {
3173 let locator = Locator::new("input");
3174 let assertion = expect(locator).to_have_attribute("type", "email");
3175 let result = assertion.validate("text");
3176 assert!(result.is_err());
3177 let err = result.unwrap_err();
3178 assert!(err.to_string().contains("email"));
3179 assert!(err.to_string().contains("type"));
3180 }
3181
3182 #[test]
3187 fn test_locator_or_non_css() {
3188 let locator1 = Locator::from_selector(Selector::Entity("hero".to_string()));
3189 let locator2 = Locator::new("div");
3190 let combined = locator1.or(locator2);
3191 assert!(matches!(combined.selector(), Selector::Entity(_)));
3193 }
3194
3195 #[test]
3196 fn test_locator_first_non_css() {
3197 let locator = Locator::from_selector(Selector::Entity("hero".to_string()));
3198 let result = locator.first();
3199 assert!(matches!(result.selector(), Selector::Entity(_)));
3201 }
3202
3203 #[test]
3204 fn test_locator_last_non_css() {
3205 let locator = Locator::from_selector(Selector::Entity("hero".to_string()));
3206 let result = locator.last();
3207 assert!(matches!(result.selector(), Selector::Entity(_)));
3209 }
3210
3211 #[test]
3212 fn test_locator_nth_non_css() {
3213 let locator = Locator::from_selector(Selector::Entity("hero".to_string()));
3214 let result = locator.nth(5);
3215 assert!(matches!(result.selector(), Selector::Entity(_)));
3217 }
3218
3219 #[test]
3224 fn test_role_with_name_count_query() {
3225 let selector = Selector::role_with_name("button", "Submit");
3226 let query = selector.to_count_query();
3227 assert!(query.contains("role"));
3228 assert!(query.contains("Submit"));
3229 assert!(query.contains(".length"));
3230 }
3231
3232 #[test]
3237 fn test_filter_without_has_text() {
3238 let child = Locator::new(".child");
3239 let locator = Locator::new("div").filter(FilterOptions::new().has(child));
3240 assert!(matches!(locator.selector(), Selector::Css(_)));
3242 }
3243
3244 #[test]
3249 fn test_click_options_default_trait() {
3250 let options: ClickOptions = Default::default();
3251 assert_eq!(options.button, MouseButton::Left);
3252 assert_eq!(options.click_count, 0); }
3254
3255 #[test]
3260 fn test_filter_options_default_trait() {
3261 let options: FilterOptions = Default::default();
3262 assert!(options.has.is_none());
3263 assert!(options.has_text.is_none());
3264 assert!(options.has_not.is_none());
3265 assert!(options.has_not_text.is_none());
3266 }
3267
3268 #[test]
3273 fn test_point_clone() {
3274 let p1 = Point::new(1.0, 2.0);
3275 let p2 = p1;
3276 assert!((p2.x - 1.0).abs() < f32::EPSILON);
3277 assert!((p2.y - 2.0).abs() < f32::EPSILON);
3278 }
3279
3280 #[test]
3281 fn test_point_partial_eq() {
3282 let p1 = Point::new(1.0, 2.0);
3283 let p2 = Point::new(1.0, 2.0);
3284 let p3 = Point::new(3.0, 4.0);
3285 assert_eq!(p1, p2);
3286 assert_ne!(p1, p3);
3287 }
3288
3289 #[test]
3294 fn test_bounding_box_clone() {
3295 let b1 = BoundingBox::new(0.0, 0.0, 100.0, 100.0);
3296 let b2 = b1;
3297 assert!((b2.width - 100.0).abs() < f32::EPSILON);
3298 }
3299
3300 #[test]
3301 fn test_bounding_box_partial_eq() {
3302 let b1 = BoundingBox::new(0.0, 0.0, 100.0, 100.0);
3303 let b2 = BoundingBox::new(0.0, 0.0, 100.0, 100.0);
3304 let b3 = BoundingBox::new(1.0, 1.0, 100.0, 100.0);
3305 assert_eq!(b1, b2);
3306 assert_ne!(b1, b3);
3307 }
3308
3309 #[test]
3314 fn test_selector_equality() {
3315 let s1 = Selector::css("button");
3316 let s2 = Selector::css("button");
3317 let s3 = Selector::css("div");
3318 assert_eq!(s1, s2);
3319 assert_ne!(s1, s3);
3320 }
3321
3322 #[test]
3323 fn test_selector_equality_css_with_text() {
3324 let s1 = Selector::CssWithText {
3325 css: "button".to_string(),
3326 text: "Click".to_string(),
3327 };
3328 let s2 = Selector::CssWithText {
3329 css: "button".to_string(),
3330 text: "Click".to_string(),
3331 };
3332 assert_eq!(s1, s2);
3333 }
3334
3335 #[test]
3336 fn test_selector_equality_role() {
3337 let s1 = Selector::Role {
3338 role: "button".to_string(),
3339 name: Some("Submit".to_string()),
3340 };
3341 let s2 = Selector::Role {
3342 role: "button".to_string(),
3343 name: Some("Submit".to_string()),
3344 };
3345 assert_eq!(s1, s2);
3346 }
3347
3348 #[test]
3353 fn test_drag_builder_full_chain() {
3354 let locator = Locator::new("div");
3355 let action = locator
3356 .drag_to(&Point::new(100.0, 200.0))
3357 .steps(15)
3358 .duration(Duration::from_millis(750))
3359 .build();
3360
3361 match action {
3362 LocatorAction::Drag {
3363 target,
3364 steps,
3365 duration,
3366 ..
3367 } => {
3368 assert!((target.x - 100.0).abs() < f32::EPSILON);
3369 assert!((target.y - 200.0).abs() < f32::EPSILON);
3370 assert_eq!(steps, 15);
3371 assert_eq!(duration, Duration::from_millis(750));
3372 }
3373 _ => panic!("Expected Drag action"),
3374 }
3375 }
3376
3377 #[test]
3382 fn test_locator_options_poll_interval() {
3383 let opts = LocatorOptions::default();
3384 assert_eq!(
3385 opts.poll_interval,
3386 Duration::from_millis(DEFAULT_POLL_INTERVAL_MS)
3387 );
3388 }
3389
3390 #[test]
3395 fn test_key_modifier_variants() {
3396 let modifiers = vec![
3397 KeyModifier::Alt,
3398 KeyModifier::Control,
3399 KeyModifier::Meta,
3400 KeyModifier::Shift,
3401 ];
3402 assert_eq!(modifiers.len(), 4);
3403
3404 assert_eq!(KeyModifier::Alt, KeyModifier::Alt);
3406 assert_ne!(KeyModifier::Alt, KeyModifier::Control);
3407 }
3408
3409 #[test]
3414 fn test_mouse_button_variants() {
3415 let buttons = vec![MouseButton::Left, MouseButton::Right, MouseButton::Middle];
3416 assert_eq!(buttons.len(), 3);
3417
3418 assert_eq!(MouseButton::Left, MouseButton::Left);
3419 assert_ne!(MouseButton::Left, MouseButton::Right);
3420 }
3421
3422 #[test]
3427 fn test_locator_action_drag_locator_accessor() {
3428 let locator = Locator::new("div.draggable");
3429 let action = locator.drag_to(&Point::new(0.0, 0.0)).build();
3430 let accessed = action.locator();
3431 assert!(matches!(accessed.selector(), Selector::Css(_)));
3432 }
3433
3434 #[test]
3439 fn test_locator_action_fill_locator_accessor() {
3440 let locator = Locator::new("input");
3441 let action = locator.fill("test").unwrap();
3442 let accessed = action.locator();
3443 assert!(matches!(accessed.selector(), Selector::Css(_)));
3444 }
3445
3446 #[test]
3451 fn test_validate_browser_context_assertions() {
3452 let locator = Locator::new("div");
3453
3454 let assertion = expect(locator.clone()).to_be_visible();
3456 assert!(assertion.validate("any").is_ok());
3457
3458 let assertion = expect(locator.clone()).to_be_hidden();
3460 assert!(assertion.validate("any").is_ok());
3461
3462 let assertion = expect(locator.clone()).to_have_count(5);
3464 assert!(assertion.validate("any").is_ok());
3465
3466 let assertion = expect(locator.clone()).to_be_enabled();
3468 assert!(assertion.validate("any").is_ok());
3469
3470 let assertion = expect(locator.clone()).to_be_disabled();
3472 assert!(assertion.validate("any").is_ok());
3473
3474 let assertion = expect(locator.clone()).to_be_checked();
3476 assert!(assertion.validate("any").is_ok());
3477
3478 let assertion = expect(locator.clone()).to_be_editable();
3480 assert!(assertion.validate("any").is_ok());
3481
3482 let assertion = expect(locator.clone()).to_be_focused();
3484 assert!(assertion.validate("any").is_ok());
3485
3486 let assertion = expect(locator.clone()).to_be_empty();
3488 assert!(assertion.validate("any").is_ok());
3489
3490 let assertion = expect(locator).to_have_css("color", "red");
3492 assert!(assertion.validate("any").is_ok());
3493 }
3494
3495 #[test]
3500 fn test_debug_implementations() {
3501 let point = Point::new(1.0, 2.0);
3502 let debug_str = format!("{:?}", point);
3503 assert!(debug_str.contains("Point"));
3504
3505 let bbox = BoundingBox::new(0.0, 0.0, 100.0, 100.0);
3506 let debug_str = format!("{:?}", bbox);
3507 assert!(debug_str.contains("BoundingBox"));
3508
3509 let selector = Selector::css("div");
3510 let debug_str = format!("{:?}", selector);
3511 assert!(debug_str.contains("Css"));
3512
3513 let locator = Locator::new("button");
3514 let debug_str = format!("{:?}", locator);
3515 assert!(debug_str.contains("Locator"));
3516
3517 let options = LocatorOptions::default();
3518 let debug_str = format!("{:?}", options);
3519 assert!(debug_str.contains("LocatorOptions"));
3520
3521 let filter = FilterOptions::new();
3522 let debug_str = format!("{:?}", filter);
3523 assert!(debug_str.contains("FilterOptions"));
3524
3525 let click_opts = ClickOptions::new();
3526 let debug_str = format!("{:?}", click_opts);
3527 assert!(debug_str.contains("ClickOptions"));
3528
3529 let drag_op = DragOperation::to(Point::new(0.0, 0.0));
3530 let debug_str = format!("{:?}", drag_op);
3531 assert!(debug_str.contains("DragOperation"));
3532
3533 let drag_builder = Locator::new("div").drag_to(&Point::new(0.0, 0.0));
3534 let debug_str = format!("{:?}", drag_builder);
3535 assert!(debug_str.contains("DragBuilder"));
3536
3537 let action = Locator::new("button").click().unwrap();
3538 let debug_str = format!("{:?}", action);
3539 assert!(debug_str.contains("Click"));
3540
3541 let query = Locator::new("span").text_content().unwrap();
3542 let debug_str = format!("{:?}", query);
3543 assert!(debug_str.contains("TextContent"));
3544
3545 let exp = Expect::new(Locator::new("div"));
3546 let debug_str = format!("{:?}", exp);
3547 assert!(debug_str.contains("Expect"));
3548
3549 let assertion = expect(Locator::new("div")).to_have_text("test");
3550 let debug_str = format!("{:?}", assertion);
3551 assert!(debug_str.contains("HasText"));
3552 }
3553
3554 #[test]
3559 fn test_clone_implementations() {
3560 let locator = Locator::new("button");
3561 let cloned = locator;
3562 assert!(matches!(cloned.selector(), Selector::Css(_)));
3563
3564 let options = LocatorOptions::default();
3565 let cloned = options;
3566 assert!(cloned.strict);
3567
3568 let filter = FilterOptions::new().has_text("test");
3569 let cloned = filter;
3570 assert!(cloned.has_text.is_some());
3571
3572 let click_opts = ClickOptions::new().button(MouseButton::Right);
3573 let cloned = click_opts;
3574 assert_eq!(cloned.button, MouseButton::Right);
3575
3576 let drag_op = DragOperation::to(Point::new(1.0, 2.0)).steps(5);
3577 let cloned = drag_op;
3578 assert_eq!(cloned.steps, 5);
3579
3580 let drag_builder = Locator::new("div").drag_to(&Point::new(3.0, 4.0)).steps(7);
3581 let cloned = drag_builder;
3582 let action = cloned.build();
3583 assert!(matches!(action, LocatorAction::Drag { steps: 7, .. }));
3584
3585 let action = Locator::new("button").hover().unwrap();
3586 let cloned = action;
3587 assert!(matches!(cloned, LocatorAction::Hover { .. }));
3588
3589 let query = Locator::new("span").count().unwrap();
3590 let cloned = query;
3591 assert!(matches!(cloned, LocatorQuery::Count { .. }));
3592
3593 let exp = Expect::new(Locator::new("div"));
3594 let cloned = exp;
3595 let _ = cloned.to_be_visible();
3596
3597 let assertion = expect(Locator::new("div")).to_have_count(3);
3598 let cloned = assertion;
3599 assert!(matches!(cloned, ExpectAssertion::HasCount { .. }));
3600 }
3601
3602 #[test]
3607 fn test_selector_to_query_special_chars() {
3608 let selector = Selector::css(r#"div[data-value="test's value"]"#);
3610 let query = selector.to_query();
3611 assert!(query.contains("querySelector"));
3612
3613 let selector =
3615 Selector::XPath(r#"//button[contains(text(), "Click here")]"#.to_string());
3616 let query = selector.to_query();
3617 assert!(query.contains("evaluate"));
3618
3619 let selector = Selector::test_id("my-test-id_123");
3621 let query = selector.to_query();
3622 assert!(query.contains("data-testid"));
3623 }
3624
3625 #[test]
3630 fn test_bounding_box_contains_all_edges() {
3631 let bbox = BoundingBox::new(10.0, 20.0, 100.0, 50.0);
3632
3633 assert!(bbox.contains(&Point::new(10.0, 20.0))); assert!(bbox.contains(&Point::new(110.0, 20.0))); assert!(bbox.contains(&Point::new(10.0, 70.0))); assert!(bbox.contains(&Point::new(110.0, 70.0))); assert!(!bbox.contains(&Point::new(9.9, 45.0))); assert!(!bbox.contains(&Point::new(110.1, 45.0))); assert!(!bbox.contains(&Point::new(55.0, 19.9))); assert!(!bbox.contains(&Point::new(55.0, 70.1))); }
3645
3646 #[test]
3651 fn test_bounding_box_center_with_offset() {
3652 let bbox = BoundingBox::new(10.0, 20.0, 100.0, 50.0);
3653 let center = bbox.center();
3654 assert!((center.x - 60.0).abs() < f32::EPSILON); assert!((center.y - 45.0).abs() < f32::EPSILON); }
3657
3658 #[test]
3663 fn test_locator_chaining_all_options() {
3664 let locator = Locator::new("button")
3665 .with_text("Click")
3666 .with_timeout(Duration::from_secs(10))
3667 .with_strict(false)
3668 .with_visible(false);
3669
3670 assert!(!locator.options().strict);
3671 assert!(!locator.options().visible);
3672 assert_eq!(locator.options().timeout, Duration::from_secs(10));
3673 assert!(matches!(locator.selector(), Selector::CssWithText { .. }));
3674 }
3675
3676 #[test]
3681 fn test_click_options_full_chain() {
3682 let options = ClickOptions::new()
3683 .button(MouseButton::Middle)
3684 .click_count(3)
3685 .position(Point::new(5.0, 10.0))
3686 .modifier(KeyModifier::Shift)
3687 .modifier(KeyModifier::Alt)
3688 .modifier(KeyModifier::Control)
3689 .modifier(KeyModifier::Meta);
3690
3691 assert_eq!(options.button, MouseButton::Middle);
3692 assert_eq!(options.click_count, 3);
3693 assert!(options.position.is_some());
3694 let pos = options.position.unwrap();
3695 assert!((pos.x - 5.0).abs() < f32::EPSILON);
3696 assert!((pos.y - 10.0).abs() < f32::EPSILON);
3697 assert_eq!(options.modifiers.len(), 4);
3698 }
3699 }
3700}