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()
}