thirtyfour_macros/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
//! Thirtyfour is a Selenium / WebDriver library for Rust, for automated website UI testing.
//!
//! This crate provides proc macros for use with [thirtyfour](https://docs.rs/thirtyfour).
//!
//!
use crate::component::expand_component_derive;
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
mod component;
/// Derive macro for a wrapped `Component`.
///
/// A `Component` contains a base [`WebElement`] from which all element queries will be performed.
///
/// All elements in the component are descendents of the base element (or at least the
/// starting point for an element query, since XPath queries can access parent nodes).
///
/// Components perform element lookups via [`ElementResolver`]s, which lazily perform an
/// element query to resolve a [`WebElement`], and then cache the result for later access.
///
/// See the [`ElementResolver`] documentation for more details.
///
/// ## Attributes
///
/// ### `#[base]`
/// By default the base element should be named `base` and be of type `WebElement`.
/// You can optionally use the `#[base]` attribute if you wish to name the base element
/// something other than `base`. If you use this attribute you cannot also have another
/// element named `base`.
///
/// ### `#[by(..)]`
/// Components use the `#[by(..)]` attribute to specify all of the details of the query.
///
/// The only required attribute is the selector attribute, which can be one of the following:
/// - `id = "..."`: Select element by id.
/// - `tag = "..."`: Select element by tag name.
/// - `link = "..."`: Select element by link text.
/// - `css = "..."`: Select element by CSS.
/// - `xpath = "..."`: Select element by XPath.
/// - `name = "..."`: Select element by name.
/// - `class = "..."`: Select element by class name.
///
/// Optional attributes available within `#[by(..)]` include:
/// - `single`: (default, single element only) Return `NoSuchElement` if the number of elements
/// found is != 1.
/// - `first`: (single element only) Select the first element that matches the query.
/// By default a query will return `NoSuchElement` if multiple elements match.
/// This default is designed to catch instances where a query is not specific enough.
/// - `not_empty`: (default, multi elements only) Return `NoSuchElement` if no elements were found.
/// - `allow_empty`: (multi elements only) Return an empty Vec if no elements were found.
/// By default a multi-element query will return `NoSuchElement` if no
/// elements were found.
/// - `description = "..."`: Set the element description to be displayed in `NoSuchElement` errors.
/// - `allow_errors`: Ignore errors such as stale elements while polling.
/// - `wait(timeout_ms = 10000, interval_ms=500)`: Override the default polling options.
/// - `nowait`: Turn off polling for this element query.
/// - `custom = "my_resolve_fn"`: Use the specified function to resolve the element or component.
/// **NOTE**: The `custom` attribute cannot be specified with any other
/// attribute.
///
/// See [`ElementQueryOptions`] for more details on how each option is used.
///
/// ### Custom resolver functions
///
/// When using `custom = "my_resolve_fn"`, your function signature should look something like this:
///
/// ```ignore
/// async fn my_resolve_fn(elem: &WebElement) -> WebDriverResult<T>
/// ```
///
/// where the `T` is the same as type `T` in `ElementResolver<T>`.
/// Also see the example below.
///
/// ## Example:
/// ```ignore
/// /// This component shows how to nest components inside others.
/// #[derive(Debug, Clone, Component)]
/// pub struct CheckboxSectionComponent {
/// base: WebElement,
/// #[by(tag = "label", allow_empty)]
/// boxes: ElementResolver<Vec<CheckboxComponent>>,
/// // Other fields will be initialised using `Default::default()`.
/// my_field: bool,
/// }
///
/// /// This component shows how to wrap a simple web component.
/// #[derive(Debug, Clone, Component)]
/// pub struct CheckboxComponent {
/// base: WebElement,
/// #[by(css = "input[type='checkbox']", first)]
/// input: ElementResolver<WebElement>,
/// #[by(name = "text-label", description = "text label")]
/// label: ElementResolver<WebElement>
/// #[by(custom = "my_custom_resolve_fn")]
/// button: ElementResolver<WebElement>
/// }
///
/// /// Use this function signature for your custom resolvers.
/// async fn my_custom_resolve_fn(elem: &WebElement) -> WebDriverResult<WebElement> {
/// // Do something with elem.
/// elem.query(By::ClassName("my-class")).and_displayed().first().await
/// }
///
/// impl CheckboxComponent {
/// /// Return true if the checkbox is ticked.
/// pub async fn is_ticked(&self) -> WebDriverResult<bool> {
/// // Equivalent to: let elem = self.input.resolve_present().await?;
/// let elem = resolve_present!(self.input);
/// let prop = elem.prop("checked").await?;
/// Ok(prop.unwrap_or_default() == "true")
/// }
/// }
/// ```
/// [`WebElement`]: https://docs.rs/thirtyfour/latest/thirtyfour/struct.WebElement.html
/// [`ElementResolver`]: https://docs.rs/thirtyfour/0.31.0-alpha.1/thirtyfour/components/struct.ElementResolver.html
/// [`ElementQueryOptions`]: https://docs.rs/thirtyfour/0.31.0-alpha.1/thirtyfour/extensions/query/struct.ElementQueryOptions.html
/// [`ElementQueryFn<T>`]: https://docs.rs/thirtyfour/0.31.0-alpha.1/thirtyfour/common/types/type.ElementQueryFn.html
#[proc_macro_derive(Component, attributes(base, by))]
#[proc_macro_error::proc_macro_error]
pub fn derive_component_fn(input: TokenStream) -> TokenStream {
let ast: DeriveInput = parse_macro_input!(input);
expand_component_derive(ast).into()
}