Locators

Probar provides Playwright-style locators for finding game elements with full Playwright parity.
Locator Strategy
┌─────────────────────────────────────────────────────────────────┐
│ LOCATOR STRATEGIES │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ CSS │ │ TestID │ │ Text │ │
│ │ Selector │ │ Selector │ │ Selector │ │
│ │ "button.x" │ │ "submit-btn"│ │ "Click me" │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────┬────┴────────────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Locator │ │
│ │ Chain │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────────┼──────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ filter │ │ and │ │ or │ │
│ │ (opts) │ │ (loc) │ │ (loc) │ │
│ └────────┘ └────────┘ └────────┘ │
│ │
│ SEMANTIC: role, label, placeholder, alt_text │
│ SPATIAL: within_radius, in_bounds, nearest_to │
│ ECS: has_component, component_matches │
│ │
└─────────────────────────────────────────────────────────────────┘
Basic Locators
#![allow(unused)] fn main() { use probar::{Locator, Selector}; // CSS selector let button = Locator::new("button.primary"); // Test ID selector (recommended for stability) let submit = Locator::by_test_id("submit-button"); // Text content let start = Locator::by_text("Start Game"); // Entity selector (WASM games) let player = Locator::from_selector(Selector::entity("player")); }
Semantic Locators (PMAT-001)
Probar supports Playwright's semantic locators for accessible testing:
#![allow(unused)] fn main() { use probar::{Locator, Selector}; // Role selector (ARIA roles) let button = Locator::by_role("button"); let link = Locator::by_role("link"); let textbox = Locator::by_role("textbox"); // Role with name filter (like Playwright's { name: 'Submit' }) let submit = Locator::by_role_with_name("button", "Submit"); // Label selector (form elements by label text) let username = Locator::by_label("Username"); let password = Locator::by_label("Password"); // Placeholder selector let search = Locator::by_placeholder("Search..."); let email = Locator::by_placeholder("Enter email"); // Alt text selector (images) let logo = Locator::by_alt_text("Company Logo"); let avatar = Locator::by_alt_text("Player Avatar"); }
Selector Variants
#![allow(unused)] fn main() { use probar::Selector; // All selector types let css = Selector::css("button.primary"); let xpath = Selector::XPath("//button[@id='submit']".into()); let text = Selector::text("Click me"); let test_id = Selector::test_id("submit-btn"); let entity = Selector::entity("hero"); // Semantic selectors let role = Selector::role("button"); let role_named = Selector::role_with_name("button", "Submit"); let label = Selector::label("Username"); let placeholder = Selector::placeholder("Search"); let alt = Selector::alt_text("Logo"); // Combined with text filter let css_text = Selector::CssWithText { css: "button".into(), text: "Submit".into(), }; }
Entity Queries
#![allow(unused)] fn main() { let platform = WebPlatform::new_for_test(config); // Find single entity let player = platform.locate(Locator::id("player"))?; let pos = platform.get_position(player); // Find all matching let coins: Vec<Entity> = platform.locate_all(Locator::tag("coin")); assert_eq!(coins.len(), 5); // First matching let first_enemy = platform.locate_first(Locator::tag("enemy")); }
Locator Operations (PMAT-002)
Probar supports Playwright's locator composition operations:
Filter
#![allow(unused)] fn main() { use probar::{Locator, FilterOptions}; // Filter with hasText let active_buttons = Locator::new("button") .filter(FilterOptions::new().has_text("Active")); // Filter with hasNotText let enabled = Locator::new("button") .filter(FilterOptions::new().has_not_text("Disabled")); // Filter with child locator let with_icon = Locator::new("button") .filter(FilterOptions::new().has(Locator::new(".icon"))); // Combined filters let opts = FilterOptions::new() .has_text("Submit") .has_not_text("Cancel"); }
And/Or Composition
#![allow(unused)] fn main() { use probar::Locator; // AND - both conditions must match (intersection) let active_button = Locator::new("button") .and(Locator::new(".active")); // Produces: "button.active" // OR - either condition can match (union) let clickable = Locator::new("button") .or(Locator::new("a.btn")); // Produces: "button, a.btn" // Chain multiple ORs let any_interactive = Locator::new("button") .or(Locator::new("a")) .or(Locator::new("[role='button']")); }
Index Operations
#![allow(unused)] fn main() { use probar::Locator; // Get first element let first_item = Locator::new("li.menu-item").first(); // Get last element let last_item = Locator::new("li.menu-item").last(); // Get nth element (0-indexed) let third_item = Locator::new("li.menu-item").nth(2); // Chained operations let second_active = Locator::new("button") .and(Locator::new(".active")) .nth(1); }
Compound Locators
#![allow(unused)] fn main() { // AND - must match all let armed_enemy = Locator::new(".enemy") .and(Locator::new(".armed")); // OR - match any let interactable = Locator::new(".door") .or(Locator::new(".chest")); // Combined with index let first_enemy = Locator::new(".enemy").first(); }
Spatial Locators
#![allow(unused)] fn main() { // Within radius let nearby = Locator::within_radius(player_pos, 100.0); // In bounds let visible = Locator::in_bounds(screen_bounds); // Nearest to point let closest_enemy = Locator::nearest_to(player_pos) .with_filter(Locator::tag("enemy")); }
Component-Based Locators
#![allow(unused)] fn main() { // Has specific component let physics_entities = Locator::has_component::<RigidBody>(); // Component matches predicate let low_health = Locator::component_matches::<Health>(|h| h.value < 20); // Has all components let complete_entities = Locator::has_all_components::<( Position, Velocity, Sprite, )>(); }
Type-Safe Locators (with derive)
Using jugar-probar-derive for compile-time checked selectors:
#![allow(unused)] fn main() { use jugar_probar_derive::Entity; #[derive(Entity)] #[entity(id = "player")] struct Player; #[derive(Entity)] #[entity(tag = "enemy")] struct Enemy; // Compile-time verified let player = platform.locate::<Player>()?; let enemies = platform.locate_all::<Enemy>(); }
Waiting for Elements
#![allow(unused)] fn main() { // Wait for entity to exist let boss = platform.wait_for( Locator::id("boss"), Duration::from_secs(5), )?; // Wait for condition platform.wait_until( || platform.locate(Locator::id("door")).is_some(), Duration::from_secs(2), )?; }
Locator Chains
#![allow(unused)] fn main() { // Find children let player_weapon = Locator::id("player") .child(Locator::tag("weapon")); // Find parent let weapon_owner = Locator::id("sword") .parent(); // Find siblings let adjacent_tiles = Locator::id("current_tile") .siblings(); }
Actions on Located Elements
#![allow(unused)] fn main() { let button = platform.locate(Locator::id("start_button"))?; // Get info let pos = platform.get_position(button); let bounds = platform.get_bounds(button); let visible = platform.is_visible(button); // Interact platform.click(button); platform.hover(button); // Check state let enabled = platform.is_enabled(button); let focused = platform.is_focused(button); }
Example Test
#![allow(unused)] fn main() { #[test] fn test_coin_collection() { let mut platform = WebPlatform::new_for_test(config); // Count initial coins let initial_coins = platform.locate_all(Locator::tag("coin")).len(); assert_eq!(initial_coins, 5); // Move player to first coin let first_coin = platform.locate_first(Locator::tag("coin")).unwrap(); let coin_pos = platform.get_position(first_coin); // Simulate movement move_player_to(&mut platform, coin_pos); // Coin should be collected let remaining_coins = platform.locate_all(Locator::tag("coin")).len(); assert_eq!(remaining_coins, 4); // Score should increase let score_display = platform.locate(Locator::id("score")).unwrap(); let score_text = platform.get_text(score_display); assert!(score_text.contains("10")); } }