pub struct ValidateBuilder<'a> { /* private fields */ }
Expand description

Used to build a Validate object, necessary to invoke the validate_page or validate_and_load_static_assets functions.

Example

use goose::prelude::*;
use goose_eggs::{validate_and_load_static_assets, Validate};

transaction!(load_and_validate_page);

async fn load_and_validate_page(user: &mut GooseUser) -> TransactionResult {
    // Make a GET request.
    let mut goose = user.get("example/path").await?;

    // Build a [`Validate`] object to confirm the response is valid.
    let validate = &Validate::builder()
        // Validate that the page has `Example` in the title.
        .title("Example")
        // Validate that the page has `foo` in the returned html body.
        .text("foo")
        // Validate that the page also has `<a href="bar">` in the returned
        // html body.
        .text(r#"<a href="bar">"#)
        .build();

    // Perform the actual validation, using `?` to pass up the error if any
    // validation fails.
    validate_and_load_static_assets(
        user,
        goose,
        &validate,
    ).await?;

    Ok(())
}

Implementations§

Define the HTTP status expected to be returned when loading the page.

This structure is passed to validate_page or validate_and_load_static_assets.

Example
use goose_eggs::Validate;

let _validate = Validate::builder()
    .status(200)
    .build();

Create a Validate object to validate that response title contains the specified text.

This structure is passed to validate_page or validate_and_load_static_assets.

Example
use goose_eggs::Validate;

let _validate = Validate::builder()
    .title("Home page")
    .build();
Examples found in repository?
examples/umami/english.rs (line 13)
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
pub async fn front_page_en(user: &mut GooseUser) -> TransactionResult {
    let goose = user.get("").await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder().title("Home").build(),
    )
    .await?;

    Ok(())
}

/// Load recipe listing in English and all static assets found on the page.
pub async fn recipe_listing_en(user: &mut GooseUser) -> TransactionResult {
    let goose = user.get("en/recipes/").await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder().title("Recipes").build(),
    )
    .await?;

    Ok(())
}

/// Load a random recipe in English and all static assets found on the page.
pub async fn recipe_en(user: &mut GooseUser) -> TransactionResult {
    let nodes = common::get_nodes(&common::ContentType::Recipe);
    let recipe = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(recipe.unwrap().url_en).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(recipe.unwrap().title_en)
            .build(),
    )
    .await?;

    Ok(())
}

/// Load article listing in English and all static assets found on the page.
pub async fn article_listing_en(user: &mut GooseUser) -> TransactionResult {
    let goose = user.get("en/articles/").await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder().title("Articles").build(),
    )
    .await?;

    Ok(())
}

/// Load a random article in English and all static assets found on the page.
pub async fn article_en(user: &mut GooseUser) -> TransactionResult {
    let nodes = common::get_nodes(&common::ContentType::Article);
    let article = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(article.unwrap().url_en).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(article.unwrap().title_en)
            .build(),
    )
    .await?;

    Ok(())
}

/// Load a random basic page in English and all static assets found on the page.
pub async fn basic_page_en(user: &mut GooseUser) -> TransactionResult {
    let nodes = common::get_nodes(&common::ContentType::BasicPage);
    let page = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(page.unwrap().url_en).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(page.unwrap().title_en)
            .build(),
    )
    .await?;

    Ok(())
}

/// Load a random node by nid in English and all static assets found on the page.
pub async fn page_by_nid(user: &mut GooseUser) -> TransactionResult {
    // Randomly select a content type.
    let content_types = vec![
        common::ContentType::Article,
        common::ContentType::BasicPage,
        common::ContentType::Recipe,
    ];
    let content_type = content_types.choose(&mut rand::thread_rng());
    // Then randomly select a node of this content type.
    let nodes = common::get_nodes(content_type.unwrap());
    let page = nodes.choose(&mut rand::thread_rng());
    // Load the page by nid instead of by URL.
    let goose = user
        .get(&("node/".to_string() + &page.unwrap().nid.to_string()))
        .await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(page.unwrap().title_en)
            .build(),
    )
    .await?;

    Ok(())
}

/// Anonymously load the contact form in English and POST feedback.
pub async fn anonymous_contact_form_en(user: &mut GooseUser) -> TransactionResult {
    common::anonymous_contact_form(user, true).await?;

    Ok(())
}

// Pick a random word from the title of a random node and perform a search in English.
pub async fn search_en(user: &mut GooseUser) -> TransactionResult {
    // Build a random three-word phrase taken from english words on the Umami website.
    let search_words = common::random_words(3, true);
    let search_phrase = search_words.join(" ");

    // The search page should have "Search" in the title.
    let validate_search_page = &goose_eggs::Validate::builder().title("Search").build();
    // The results page should have the search_phrase in the title.
    let validate_results_page = &goose_eggs::Validate::builder()
        .title(&*search_phrase)
        .build();
    let search_params = goose_eggs::drupal::SearchParams::builder()
        .keys(&*search_phrase)
        .url("en/search/node")
        .search_page_validation(validate_search_page)
        .results_page_validation(validate_results_page)
        .build();
    goose_eggs::drupal::search(user, &search_params).await?;

    Ok(())
}

/// Load category listing by a random term in English and all static assets found on the page.
pub async fn term_listing_en(user: &mut GooseUser) -> TransactionResult {
    let terms = common::get_terms();
    let term = terms.choose(&mut rand::thread_rng());
    let goose = user.get(term.unwrap().url_en).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(term.unwrap().title_en)
            .build(),
    )
    .await?;

    Ok(())
}
More examples
Hide additional examples
examples/umami/spanish.rs (line 13)
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
pub async fn front_page_es(user: &mut GooseUser) -> TransactionResult {
    let goose = user.get("es").await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder().title("Inicio").build(),
    )
    .await?;

    Ok(())
}

/// Load article listing in Spanish and all static assets found on the page.
pub async fn recipe_listing_es(user: &mut GooseUser) -> TransactionResult {
    let goose = user.get("es/recipes/").await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder().title("Recetas").build(),
    )
    .await?;

    Ok(())
}

/// Load a random recipe in Spanish and all static assets found on the page.
pub async fn recipe_es(user: &mut GooseUser) -> TransactionResult {
    let nodes = common::get_nodes(&common::ContentType::Recipe);
    let recipe = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(recipe.unwrap().url_es).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(recipe.unwrap().title_es)
            .build(),
    )
    .await?;

    Ok(())
}

/// Load article listing in Spanish and all static assets found on the page.
pub async fn article_listing_es(user: &mut GooseUser) -> TransactionResult {
    let goose = user.get("es/articles/").await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder().title("Artículos").build(),
    )
    .await?;

    Ok(())
}

/// Load a random article in Spanish and all static assets found on the page.
pub async fn article_es(user: &mut GooseUser) -> TransactionResult {
    let nodes = common::get_nodes(&common::ContentType::Article);
    let article = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(article.unwrap().url_es).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(article.unwrap().title_es)
            .build(),
    )
    .await?;

    Ok(())
}

/// Load a basic page in Spanish and all static assets found on the page.
pub async fn basic_page_es(user: &mut GooseUser) -> TransactionResult {
    let nodes = common::get_nodes(&common::ContentType::BasicPage);
    let page = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(page.unwrap().url_es).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(page.unwrap().title_es)
            .build(),
    )
    .await?;

    Ok(())
}

/// Anonymously load the contact form in Spanish and POST feedback.
pub async fn anonymous_contact_form_es(user: &mut GooseUser) -> TransactionResult {
    common::anonymous_contact_form(user, false).await?;

    Ok(())
}

// Pick a random word from the title of a random node and perform a search in Spanish.
pub async fn search_es(user: &mut GooseUser) -> TransactionResult {
    // Build a random three-word phrase taken from Spanish words on the Umami website.
    let search_words = common::random_words(3, false);
    let search_phrase = search_words.join(" ");

    // The search page should have "Buscar" in the title.
    let validate_search_page = &goose_eggs::Validate::builder().title("Buscar").build();
    // The results page should have the search_phrase in the title.
    let validate_results_page = &goose_eggs::Validate::builder()
        .title(&*search_phrase)
        .build();
    let search_params = goose_eggs::drupal::SearchParams::builder()
        .keys(&*search_phrase)
        .url("es/search/node")
        .search_page_validation(validate_search_page)
        .results_page_validation(validate_results_page)
        .build();
    goose_eggs::drupal::search(user, &search_params).await?;

    Ok(())
}

/// Load category listing by a random term in Spanish and all static assets found on the page.
pub async fn term_listing_es(user: &mut GooseUser) -> TransactionResult {
    let terms = common::get_terms();
    let term = terms.choose(&mut rand::thread_rng());
    let goose = user.get(term.unwrap().url_es).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(term.unwrap().title_es)
            .build(),
    )
    .await?;

    Ok(())
}
examples/umami/admin.rs (line 41)
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
pub async fn edit_article(user: &mut GooseUser) -> TransactionResult {
    // First, load a random article.
    let nodes = common::get_nodes(&common::ContentType::Article);
    let article = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(article.unwrap().url_en).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(article.unwrap().title_en)
            .text(&format!("en/node/{}/edit", article.unwrap().nid))
            .build(),
    )
    .await?;

    // Next, load the edit link for the chosen article.
    let goose = user
        .get(&format!("en/node/{}/edit", article.unwrap().nid))
        .await?;

    let edit_page = goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title("Edit Article")
            .build(),
    )
    .await?;

    let edit_form = goose_eggs::drupal::get_form(&edit_page, "node-article-edit-form");
    let form_build_id = goose_eggs::drupal::get_form_value(&edit_form, "form_build_id");
    let form_token = goose_eggs::drupal::get_form_value(&edit_form, "form_token");
    let form_id = goose_eggs::drupal::get_form_value(&edit_form, "form_id");

    // Build node form with random word from title.
    let params = [
        ("form_build_id", &form_build_id),
        ("form_token", &form_token),
        ("form_id", &form_id),
        ("op", &"Save (this translation)".to_string()),
    ];
    let mut saved_article = user
        .post_form(&format!("en/node/{}/edit", article.unwrap().nid), &params)
        .await?;

    // A successful node save is redirected.
    if !saved_article.request.redirected {
        return user.set_failure(
            &format!("{}: saving article failed", saved_article.request.final_url),
            &mut saved_article.request,
            None,
            None,
        );
    }

    // Be sure we're viewing the same article after editing it.
    goose_eggs::validate_and_load_static_assets(
        user,
        saved_article,
        &goose_eggs::Validate::builder()
            .title(article.unwrap().title_en)
            .build(),
    )
    .await?;

    Ok(())
}
examples/umami/common.rs (lines 423-427)
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
pub async fn anonymous_contact_form(user: &mut GooseUser, english: bool) -> TransactionResult {
    let contact_form_url = if english { "en/contact" } else { "es/contact" };
    let goose = user.get(contact_form_url).await?;
    let contact_page = goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(if english {
                "Website feedback"
            } else {
                "Comentarios sobre el sitio web"
            })
            .build(),
    )
    .await?;

    let form = goose_eggs::drupal::get_form(&contact_page, "contact-message-feedback-form");
    let form_build_id = goose_eggs::drupal::get_form_value(&form, "form_build_id");
    let form_id = goose_eggs::drupal::get_form_value(&form, "form_id");

    // Build contact form parameters.
    let name = random_words(2, english).join(" ");
    let email = format!("{}@example.com", random_words(1, english).pop().unwrap());
    let subject = random_words(8, english).join(" ");
    let message = random_words(12, english).join(" ");
    let params = [
        ("name", name.as_str()),
        ("mail", email.as_str()),
        ("subject[0][value]", subject.as_str()),
        ("message[0][value]", message.as_str()),
        ("form_build_id", &form_build_id),
        ("form_id", &form_id),
        ("op", "Send+message"),
    ];
    let mut goose = user.post_form(contact_form_url, &params).await?;

    // Drupal 9 throttles how many times an IP address can submit the contact form, so we
    // need special handling.
    match goose.response {
        Ok(response) => {
            // Copy the headers so we have them for logging if there are errors.
            let headers = &response.headers().clone();
            match response.text().await {
                Ok(html) => {
                    // Drupal 9 will throttle how many times our IP address can actually
                    // submit the contact form. We can detect this, but it happens a lot
                    // so there's nothing useful to do.
                    let error_text = if english {
                        "You cannot send more than"
                    } else {
                        "No le está permitido enviar más"
                    };
                    if html.contains(error_text) {
                        // The contact form was throttled, safely ignore this.
                    }

                    // Either way, a "real" user would still load all static elements on
                    // the returned page.
                    goose_eggs::load_static_elements(user, &html).await;
                }
                Err(e) => {
                    return user.set_failure(
                        &format!("{}: failed to parse page: {}", goose.request.raw.url, e),
                        &mut goose.request,
                        Some(headers),
                        None,
                    );
                }
            }
        }
        Err(e) => {
            return user.set_failure(
                &format!("{}: no response from server: {}", goose.request.raw.url, e),
                &mut goose.request,
                None,
                None,
            );
        }
    }

    Ok(())
}

Create a Validate object to validate that the response page contains the specified text.

This structure is passed to validate_page or validate_and_load_static_assets.

Example
use goose_eggs::Validate;

let _validate = Validate::builder()
    .text("example")
    .build();

It’s possible to call this function multiple times to validate that multiple texts appear on the page. Alternatively you can call ValidateBuilder::texts.

Multiple Example
use goose_eggs::Validate;

let _validate = Validate::builder()
    .text("example")
    .text("another")
    .build();
Examples found in repository?
examples/umami/admin.rs (line 42)
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
pub async fn edit_article(user: &mut GooseUser) -> TransactionResult {
    // First, load a random article.
    let nodes = common::get_nodes(&common::ContentType::Article);
    let article = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(article.unwrap().url_en).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(article.unwrap().title_en)
            .text(&format!("en/node/{}/edit", article.unwrap().nid))
            .build(),
    )
    .await?;

    // Next, load the edit link for the chosen article.
    let goose = user
        .get(&format!("en/node/{}/edit", article.unwrap().nid))
        .await?;

    let edit_page = goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title("Edit Article")
            .build(),
    )
    .await?;

    let edit_form = goose_eggs::drupal::get_form(&edit_page, "node-article-edit-form");
    let form_build_id = goose_eggs::drupal::get_form_value(&edit_form, "form_build_id");
    let form_token = goose_eggs::drupal::get_form_value(&edit_form, "form_token");
    let form_id = goose_eggs::drupal::get_form_value(&edit_form, "form_id");

    // Build node form with random word from title.
    let params = [
        ("form_build_id", &form_build_id),
        ("form_token", &form_token),
        ("form_id", &form_id),
        ("op", &"Save (this translation)".to_string()),
    ];
    let mut saved_article = user
        .post_form(&format!("en/node/{}/edit", article.unwrap().nid), &params)
        .await?;

    // A successful node save is redirected.
    if !saved_article.request.redirected {
        return user.set_failure(
            &format!("{}: saving article failed", saved_article.request.final_url),
            &mut saved_article.request,
            None,
            None,
        );
    }

    // Be sure we're viewing the same article after editing it.
    goose_eggs::validate_and_load_static_assets(
        user,
        saved_article,
        &goose_eggs::Validate::builder()
            .title(article.unwrap().title_en)
            .build(),
    )
    .await?;

    Ok(())
}

Create a Validate object to validate that the response page contains the specified texts.

This structure is passed to validate_page or validate_and_load_static_assets.

Example
use goose_eggs::Validate;

let _validate = Validate::builder()
    .texts(vec!["example", "another"])
    .build();

Alternatively you can call ValidateBuilder::text.

Create a Validate object to validate that the response includes the specified header.

To validate that a header contains a specific value (instead of just validating that it exists), use ValidateBuilder::header_value.

This structure is passed to validate_page or validate_and_load_static_assets.

Example
use goose_eggs::Validate;

let _validate = Validate::builder()
    .header("x-cache")
    .build();

It’s possible to call this function multiple times to validate that multiple headers are set.

Multiple Example
use goose_eggs::Validate;

let _validate = Validate::builder()
    .header("x-cache")
    .header("x-generator")
    .build();

Create a Validate object to validate that the response includes the specified header which contains the specified value.

To validate that a header simply exists without confirming that it contains a specific value, use ValidateBuilder::header.

This structure is passed to validate_page or validate_and_load_static_assets.

Example
use goose_eggs::Validate;

let _validate = Validate::builder()
    .header_value("x-generator", "Drupal 7")
    .build();

It’s possible to call this function multiple times, and/or together with ValidateBuilder::header to validate that multiple headers are set and their values.

Multiple Example
use goose_eggs::Validate;

let _validate = Validate::builder()
    // Validate that the "x-cache" header is set.
    .header("x-cache")
    // Validate that the "x-generator" header is set and contains "Drupal 7".
    .header_value("x-generator", "Drupal-7")
    // Validate that the "x-drupal-cache" header is set and contains "HIT".
    .header_value("x-drupal-cache", "HIT")
    .build();

Create a Validate object to validate whether or not the response page redirected.

This structure is passed to validate_page or validate_and_load_static_assets.

Example
use goose_eggs::Validate;

// Verify the response redirected.
let _validate = Validate::builder().redirect(true).build();

// Verify the response did not redirect.
let _validate = Validate::builder().redirect(false).build();

Build the Validate object which is then passed to the validate_page or validate_and_load_static_assets functions.

Example
use goose_eggs::Validate;

// Use the default search form to search for `example keys`.
let _validate = Validate::builder()
    .text("example text")
    .build();
Examples found in repository?
examples/umami/english.rs (line 13)
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
pub async fn front_page_en(user: &mut GooseUser) -> TransactionResult {
    let goose = user.get("").await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder().title("Home").build(),
    )
    .await?;

    Ok(())
}

/// Load recipe listing in English and all static assets found on the page.
pub async fn recipe_listing_en(user: &mut GooseUser) -> TransactionResult {
    let goose = user.get("en/recipes/").await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder().title("Recipes").build(),
    )
    .await?;

    Ok(())
}

/// Load a random recipe in English and all static assets found on the page.
pub async fn recipe_en(user: &mut GooseUser) -> TransactionResult {
    let nodes = common::get_nodes(&common::ContentType::Recipe);
    let recipe = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(recipe.unwrap().url_en).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(recipe.unwrap().title_en)
            .build(),
    )
    .await?;

    Ok(())
}

/// Load article listing in English and all static assets found on the page.
pub async fn article_listing_en(user: &mut GooseUser) -> TransactionResult {
    let goose = user.get("en/articles/").await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder().title("Articles").build(),
    )
    .await?;

    Ok(())
}

/// Load a random article in English and all static assets found on the page.
pub async fn article_en(user: &mut GooseUser) -> TransactionResult {
    let nodes = common::get_nodes(&common::ContentType::Article);
    let article = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(article.unwrap().url_en).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(article.unwrap().title_en)
            .build(),
    )
    .await?;

    Ok(())
}

/// Load a random basic page in English and all static assets found on the page.
pub async fn basic_page_en(user: &mut GooseUser) -> TransactionResult {
    let nodes = common::get_nodes(&common::ContentType::BasicPage);
    let page = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(page.unwrap().url_en).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(page.unwrap().title_en)
            .build(),
    )
    .await?;

    Ok(())
}

/// Load a random node by nid in English and all static assets found on the page.
pub async fn page_by_nid(user: &mut GooseUser) -> TransactionResult {
    // Randomly select a content type.
    let content_types = vec![
        common::ContentType::Article,
        common::ContentType::BasicPage,
        common::ContentType::Recipe,
    ];
    let content_type = content_types.choose(&mut rand::thread_rng());
    // Then randomly select a node of this content type.
    let nodes = common::get_nodes(content_type.unwrap());
    let page = nodes.choose(&mut rand::thread_rng());
    // Load the page by nid instead of by URL.
    let goose = user
        .get(&("node/".to_string() + &page.unwrap().nid.to_string()))
        .await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(page.unwrap().title_en)
            .build(),
    )
    .await?;

    Ok(())
}

/// Anonymously load the contact form in English and POST feedback.
pub async fn anonymous_contact_form_en(user: &mut GooseUser) -> TransactionResult {
    common::anonymous_contact_form(user, true).await?;

    Ok(())
}

// Pick a random word from the title of a random node and perform a search in English.
pub async fn search_en(user: &mut GooseUser) -> TransactionResult {
    // Build a random three-word phrase taken from english words on the Umami website.
    let search_words = common::random_words(3, true);
    let search_phrase = search_words.join(" ");

    // The search page should have "Search" in the title.
    let validate_search_page = &goose_eggs::Validate::builder().title("Search").build();
    // The results page should have the search_phrase in the title.
    let validate_results_page = &goose_eggs::Validate::builder()
        .title(&*search_phrase)
        .build();
    let search_params = goose_eggs::drupal::SearchParams::builder()
        .keys(&*search_phrase)
        .url("en/search/node")
        .search_page_validation(validate_search_page)
        .results_page_validation(validate_results_page)
        .build();
    goose_eggs::drupal::search(user, &search_params).await?;

    Ok(())
}

/// Load category listing by a random term in English and all static assets found on the page.
pub async fn term_listing_en(user: &mut GooseUser) -> TransactionResult {
    let terms = common::get_terms();
    let term = terms.choose(&mut rand::thread_rng());
    let goose = user.get(term.unwrap().url_en).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(term.unwrap().title_en)
            .build(),
    )
    .await?;

    Ok(())
}
More examples
Hide additional examples
examples/umami/spanish.rs (line 13)
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
pub async fn front_page_es(user: &mut GooseUser) -> TransactionResult {
    let goose = user.get("es").await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder().title("Inicio").build(),
    )
    .await?;

    Ok(())
}

/// Load article listing in Spanish and all static assets found on the page.
pub async fn recipe_listing_es(user: &mut GooseUser) -> TransactionResult {
    let goose = user.get("es/recipes/").await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder().title("Recetas").build(),
    )
    .await?;

    Ok(())
}

/// Load a random recipe in Spanish and all static assets found on the page.
pub async fn recipe_es(user: &mut GooseUser) -> TransactionResult {
    let nodes = common::get_nodes(&common::ContentType::Recipe);
    let recipe = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(recipe.unwrap().url_es).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(recipe.unwrap().title_es)
            .build(),
    )
    .await?;

    Ok(())
}

/// Load article listing in Spanish and all static assets found on the page.
pub async fn article_listing_es(user: &mut GooseUser) -> TransactionResult {
    let goose = user.get("es/articles/").await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder().title("Artículos").build(),
    )
    .await?;

    Ok(())
}

/// Load a random article in Spanish and all static assets found on the page.
pub async fn article_es(user: &mut GooseUser) -> TransactionResult {
    let nodes = common::get_nodes(&common::ContentType::Article);
    let article = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(article.unwrap().url_es).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(article.unwrap().title_es)
            .build(),
    )
    .await?;

    Ok(())
}

/// Load a basic page in Spanish and all static assets found on the page.
pub async fn basic_page_es(user: &mut GooseUser) -> TransactionResult {
    let nodes = common::get_nodes(&common::ContentType::BasicPage);
    let page = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(page.unwrap().url_es).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(page.unwrap().title_es)
            .build(),
    )
    .await?;

    Ok(())
}

/// Anonymously load the contact form in Spanish and POST feedback.
pub async fn anonymous_contact_form_es(user: &mut GooseUser) -> TransactionResult {
    common::anonymous_contact_form(user, false).await?;

    Ok(())
}

// Pick a random word from the title of a random node and perform a search in Spanish.
pub async fn search_es(user: &mut GooseUser) -> TransactionResult {
    // Build a random three-word phrase taken from Spanish words on the Umami website.
    let search_words = common::random_words(3, false);
    let search_phrase = search_words.join(" ");

    // The search page should have "Buscar" in the title.
    let validate_search_page = &goose_eggs::Validate::builder().title("Buscar").build();
    // The results page should have the search_phrase in the title.
    let validate_results_page = &goose_eggs::Validate::builder()
        .title(&*search_phrase)
        .build();
    let search_params = goose_eggs::drupal::SearchParams::builder()
        .keys(&*search_phrase)
        .url("es/search/node")
        .search_page_validation(validate_search_page)
        .results_page_validation(validate_results_page)
        .build();
    goose_eggs::drupal::search(user, &search_params).await?;

    Ok(())
}

/// Load category listing by a random term in Spanish and all static assets found on the page.
pub async fn term_listing_es(user: &mut GooseUser) -> TransactionResult {
    let terms = common::get_terms();
    let term = terms.choose(&mut rand::thread_rng());
    let goose = user.get(term.unwrap().url_es).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(term.unwrap().title_es)
            .build(),
    )
    .await?;

    Ok(())
}
examples/umami/admin.rs (line 43)
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
pub async fn edit_article(user: &mut GooseUser) -> TransactionResult {
    // First, load a random article.
    let nodes = common::get_nodes(&common::ContentType::Article);
    let article = nodes.choose(&mut rand::thread_rng());
    let goose = user.get(article.unwrap().url_en).await?;
    goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(article.unwrap().title_en)
            .text(&format!("en/node/{}/edit", article.unwrap().nid))
            .build(),
    )
    .await?;

    // Next, load the edit link for the chosen article.
    let goose = user
        .get(&format!("en/node/{}/edit", article.unwrap().nid))
        .await?;

    let edit_page = goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title("Edit Article")
            .build(),
    )
    .await?;

    let edit_form = goose_eggs::drupal::get_form(&edit_page, "node-article-edit-form");
    let form_build_id = goose_eggs::drupal::get_form_value(&edit_form, "form_build_id");
    let form_token = goose_eggs::drupal::get_form_value(&edit_form, "form_token");
    let form_id = goose_eggs::drupal::get_form_value(&edit_form, "form_id");

    // Build node form with random word from title.
    let params = [
        ("form_build_id", &form_build_id),
        ("form_token", &form_token),
        ("form_id", &form_id),
        ("op", &"Save (this translation)".to_string()),
    ];
    let mut saved_article = user
        .post_form(&format!("en/node/{}/edit", article.unwrap().nid), &params)
        .await?;

    // A successful node save is redirected.
    if !saved_article.request.redirected {
        return user.set_failure(
            &format!("{}: saving article failed", saved_article.request.final_url),
            &mut saved_article.request,
            None,
            None,
        );
    }

    // Be sure we're viewing the same article after editing it.
    goose_eggs::validate_and_load_static_assets(
        user,
        saved_article,
        &goose_eggs::Validate::builder()
            .title(article.unwrap().title_en)
            .build(),
    )
    .await?;

    Ok(())
}
examples/umami/common.rs (line 428)
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
pub async fn anonymous_contact_form(user: &mut GooseUser, english: bool) -> TransactionResult {
    let contact_form_url = if english { "en/contact" } else { "es/contact" };
    let goose = user.get(contact_form_url).await?;
    let contact_page = goose_eggs::validate_and_load_static_assets(
        user,
        goose,
        &goose_eggs::Validate::builder()
            .title(if english {
                "Website feedback"
            } else {
                "Comentarios sobre el sitio web"
            })
            .build(),
    )
    .await?;

    let form = goose_eggs::drupal::get_form(&contact_page, "contact-message-feedback-form");
    let form_build_id = goose_eggs::drupal::get_form_value(&form, "form_build_id");
    let form_id = goose_eggs::drupal::get_form_value(&form, "form_id");

    // Build contact form parameters.
    let name = random_words(2, english).join(" ");
    let email = format!("{}@example.com", random_words(1, english).pop().unwrap());
    let subject = random_words(8, english).join(" ");
    let message = random_words(12, english).join(" ");
    let params = [
        ("name", name.as_str()),
        ("mail", email.as_str()),
        ("subject[0][value]", subject.as_str()),
        ("message[0][value]", message.as_str()),
        ("form_build_id", &form_build_id),
        ("form_id", &form_id),
        ("op", "Send+message"),
    ];
    let mut goose = user.post_form(contact_form_url, &params).await?;

    // Drupal 9 throttles how many times an IP address can submit the contact form, so we
    // need special handling.
    match goose.response {
        Ok(response) => {
            // Copy the headers so we have them for logging if there are errors.
            let headers = &response.headers().clone();
            match response.text().await {
                Ok(html) => {
                    // Drupal 9 will throttle how many times our IP address can actually
                    // submit the contact form. We can detect this, but it happens a lot
                    // so there's nothing useful to do.
                    let error_text = if english {
                        "You cannot send more than"
                    } else {
                        "No le está permitido enviar más"
                    };
                    if html.contains(error_text) {
                        // The contact form was throttled, safely ignore this.
                    }

                    // Either way, a "real" user would still load all static elements on
                    // the returned page.
                    goose_eggs::load_static_elements(user, &html).await;
                }
                Err(e) => {
                    return user.set_failure(
                        &format!("{}: failed to parse page: {}", goose.request.raw.url, e),
                        &mut goose.request,
                        Some(headers),
                        None,
                    );
                }
            }
        }
        Err(e) => {
            return user.set_failure(
                &format!("{}: no response from server: {}", goose.request.raw.url, e),
                &mut goose.request,
                None,
                None,
            );
        }
    }

    Ok(())
}

Trait Implementations§

Returns a copy of the value. Read more
Performs copy-assignment from source. Read more
Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more
Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.

Returns the argument unchanged.

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Instruments this type with the current Span, returning an Instrumented wrapper. Read more

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Should always be Self
The resulting type after obtaining ownership.
Creates owned data from borrowed data, usually by cloning. Read more
Uses borrowed data to replace owned data, usually by cloning. Read more
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more