bitwarden_test_macro/lib.rs
1//! Proc macro for Play test framework
2//!
3//! Provides the `#[play_test]` attribute macro for writing E2E tests with automatic
4//! Play instance setup and cleanup.
5
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::{ItemFn, parse_macro_input};
9
10/// Attribute macro for Play framework tests
11///
12/// Transforms an async test function that takes a `Play` parameter into a properly
13/// configured tokio test with automatic cleanup.
14///
15/// # Example
16///
17/// ```ignore
18/// use bitwarden_test::play::{play_test, Play, SingleUserArgs, SingleUserScene};
19///
20/// #[play_test]
21/// async fn test_user_login(play: Play) {
22/// let args = SingleUserArgs {
23/// email: "[email protected]".to_string(),
24/// ..Default::default()
25/// };
26/// let scene = play.scene::<SingleUserScene>(&args).await.unwrap();
27/// // Cleanup happens automatically when the test completes
28/// }
29/// ```
30///
31/// The macro transforms the above into:
32///
33/// ```ignore
34/// #[tokio::test]
35/// async fn test_user_login() {
36/// ::bitwarden_test::play::Play::builder()
37/// .run(|play: Play| async move {
38/// // original test body
39/// })
40/// .await;
41/// }
42/// ```
43#[proc_macro_attribute]
44pub fn play_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
45 let input = parse_macro_input!(item as ItemFn);
46
47 let fn_name = &input.sig.ident;
48 let fn_body = &input.block;
49 let fn_vis = &input.vis;
50 let fn_attrs = &input.attrs;
51
52 // Extract the play parameter name and type from the function signature
53 let (play_param, play_type) = input
54 .sig
55 .inputs
56 .first()
57 .and_then(|arg| {
58 if let syn::FnArg::Typed(pat_type) = arg {
59 if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
60 return Some((pat_ident.ident.clone(), pat_type.ty.clone()));
61 }
62 }
63 None
64 })
65 .unwrap_or_else(|| {
66 let ident = syn::Ident::new("play", proc_macro2::Span::call_site());
67 let ty: syn::Type = syn::parse_quote!(::bitwarden_test::play::Play);
68 (ident, Box::new(ty))
69 });
70
71 let expanded = quote! {
72 #(#fn_attrs)*
73 #[::tokio::test]
74 #fn_vis async fn #fn_name() {
75 ::bitwarden_test::play::Play::builder()
76 .run(|#play_param: #play_type| async move #fn_body)
77 .await;
78 }
79 };
80
81 TokenStream::from(expanded)
82}