test: add e2e browser tests for core user flows
Roaster creation (basic + all fields), full brew chain through browser, entity deletion with confirm dialog, AI extraction via text prompt with mocked OpenRouter, and check-in wizard with saved cafe + existing roast.
This commit is contained in:
parent
73976cbe51
commit
104ebb410a
5 changed files with 527 additions and 0 deletions
182
tests/e2e/brew_tests.rs
Normal file
182
tests/e2e/brew_tests.rs
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
use thirtyfour::prelude::*;
|
||||||
|
|
||||||
|
use crate::helpers::auth::authenticate_browser;
|
||||||
|
use crate::helpers::browser::BrowserSession;
|
||||||
|
use crate::helpers::forms::{
|
||||||
|
fill_input, fill_textarea, select_option, select_searchable, submit_visible_form,
|
||||||
|
};
|
||||||
|
use crate::helpers::server_helpers::{
|
||||||
|
create_default_bag, create_default_gear, create_default_roast, create_default_roaster,
|
||||||
|
spawn_app_with_auth,
|
||||||
|
};
|
||||||
|
use crate::helpers::wait::{wait_for_url_contains, wait_for_visible};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn create_brew_with_api_prerequisites() {
|
||||||
|
let app = spawn_app_with_auth().await;
|
||||||
|
|
||||||
|
// Create prerequisites via API (fast, no browser needed)
|
||||||
|
let roaster = create_default_roaster(&app).await;
|
||||||
|
let roast = create_default_roast(&app, roaster.id).await;
|
||||||
|
let _bag = create_default_bag(&app, roast.id).await;
|
||||||
|
let _grinder = create_default_gear(&app, "grinder", "Comandante", "C40 MK4").await;
|
||||||
|
let _brewer = create_default_gear(&app, "brewer", "Hario", "V60").await;
|
||||||
|
|
||||||
|
let session = BrowserSession::new(&app.address).await.unwrap();
|
||||||
|
authenticate_browser(&session, &app).await.unwrap();
|
||||||
|
|
||||||
|
// Navigate to add page with brew tab
|
||||||
|
session.goto("/add?type=brew").await.unwrap();
|
||||||
|
|
||||||
|
// Wait for the brew form's bag select to appear
|
||||||
|
wait_for_visible(&session.driver, "searchable-select[name='bag_id']")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Select the bag (displays the roast name "Test Roast")
|
||||||
|
select_searchable(&session.driver, "bag_id", "Test Roast")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// The grinder and brewer are pre-selected (only one option each).
|
||||||
|
// Default values for weight, temp, volume, time are pre-filled via signals.
|
||||||
|
// Submit the form.
|
||||||
|
submit_visible_form(&session.driver).await.unwrap();
|
||||||
|
|
||||||
|
// Should redirect to the brew detail page
|
||||||
|
wait_for_url_contains(&session.driver, "/brews/")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Verify the brew detail page loaded
|
||||||
|
let body = session.driver.find(By::Css("body")).await.unwrap();
|
||||||
|
let body_text = body.text().await.unwrap();
|
||||||
|
assert!(
|
||||||
|
body_text.contains("Test Roast"),
|
||||||
|
"Brew detail should show the roast name"
|
||||||
|
);
|
||||||
|
|
||||||
|
session.quit().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn create_full_chain_via_browser() {
|
||||||
|
let app = spawn_app_with_auth().await;
|
||||||
|
let session = BrowserSession::new(&app.address).await.unwrap();
|
||||||
|
authenticate_browser(&session, &app).await.unwrap();
|
||||||
|
|
||||||
|
// Step 1: Create roaster
|
||||||
|
session.goto("/add?type=roaster").await.unwrap();
|
||||||
|
wait_for_visible(&session.driver, "input[name='name']")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "name", "Chain Roasters")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "country", "Ethiopia")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
submit_visible_form(&session.driver).await.unwrap();
|
||||||
|
wait_for_url_contains(&session.driver, "/roasters/")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Step 2: Create grinder
|
||||||
|
session.goto("/add?type=gear").await.unwrap();
|
||||||
|
wait_for_visible(&session.driver, "select[name='category']")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
select_option(&session.driver, "category", "grinder")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "make", "Comandante")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "model", "C40").await.unwrap();
|
||||||
|
submit_visible_form(&session.driver).await.unwrap();
|
||||||
|
wait_for_url_contains(&session.driver, "/gear/")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Step 3: Create brewer
|
||||||
|
session.goto("/add?type=gear").await.unwrap();
|
||||||
|
wait_for_visible(&session.driver, "select[name='category']")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
select_option(&session.driver, "category", "brewer")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "make", "Hario").await.unwrap();
|
||||||
|
fill_input(&session.driver, "model", "V60").await.unwrap();
|
||||||
|
submit_visible_form(&session.driver).await.unwrap();
|
||||||
|
wait_for_url_contains(&session.driver, "/gear/")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Step 4: Create roast
|
||||||
|
session.goto("/add?type=roast").await.unwrap();
|
||||||
|
wait_for_visible(&session.driver, "searchable-select[name='roaster_id']")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
select_searchable(&session.driver, "roaster_id", "Chain")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "name", "Chain Blend")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "origin", "Ethiopia")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "region", "Guji").await.unwrap();
|
||||||
|
fill_input(&session.driver, "producer", "Coop")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "process", "Washed")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_textarea(&session.driver, "tasting_notes", "Blueberry, Jasmine")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
submit_visible_form(&session.driver).await.unwrap();
|
||||||
|
// Roast creation redirects to the roaster detail page
|
||||||
|
wait_for_url_contains(&session.driver, "/roasters/")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Step 5: Create bag
|
||||||
|
session.goto("/add?type=bag").await.unwrap();
|
||||||
|
wait_for_visible(&session.driver, "searchable-select[name='roast_id']")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
select_searchable(&session.driver, "roast_id", "Chain Blend")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "amount", "250").await.unwrap();
|
||||||
|
submit_visible_form(&session.driver).await.unwrap();
|
||||||
|
wait_for_url_contains(&session.driver, "/bags/")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Step 6: Create brew
|
||||||
|
session.goto("/add?type=brew").await.unwrap();
|
||||||
|
wait_for_visible(&session.driver, "searchable-select[name='bag_id']")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
select_searchable(&session.driver, "bag_id", "Chain Blend")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
submit_visible_form(&session.driver).await.unwrap();
|
||||||
|
wait_for_url_contains(&session.driver, "/brews/")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Verify the brew detail page shows the roast name
|
||||||
|
let body = session.driver.find(By::Css("body")).await.unwrap();
|
||||||
|
let body_text = body.text().await.unwrap();
|
||||||
|
assert!(
|
||||||
|
body_text.contains("Chain Blend"),
|
||||||
|
"Brew detail should show the roast name"
|
||||||
|
);
|
||||||
|
|
||||||
|
session.quit().await;
|
||||||
|
}
|
||||||
89
tests/e2e/checkin_tests.rs
Normal file
89
tests/e2e/checkin_tests.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
use thirtyfour::prelude::*;
|
||||||
|
|
||||||
|
use crate::helpers::auth::authenticate_browser;
|
||||||
|
use crate::helpers::browser::BrowserSession;
|
||||||
|
use crate::helpers::forms::select_searchable;
|
||||||
|
use crate::helpers::server_helpers::{
|
||||||
|
create_default_cafe, create_default_roast, create_default_roaster, spawn_app_with_auth,
|
||||||
|
};
|
||||||
|
use crate::helpers::wait::{wait_for_url_contains, wait_for_visible};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn checkin_with_saved_cafe_and_existing_roast() {
|
||||||
|
let app = spawn_app_with_auth().await;
|
||||||
|
|
||||||
|
// Pre-create entities via API
|
||||||
|
let _cafe = create_default_cafe(&app).await;
|
||||||
|
let roaster = create_default_roaster(&app).await;
|
||||||
|
let _roast = create_default_roast(&app, roaster.id).await;
|
||||||
|
|
||||||
|
let session = BrowserSession::new(&app.address).await.unwrap();
|
||||||
|
authenticate_browser(&session, &app).await.unwrap();
|
||||||
|
|
||||||
|
// Navigate to check-in page
|
||||||
|
session.goto("/check-in").await.unwrap();
|
||||||
|
|
||||||
|
// Step 1: Select a saved cafe
|
||||||
|
// Wait for the saved cafes searchable-select to be visible
|
||||||
|
wait_for_visible(&session.driver, "searchable-select[name='saved_cafe_id']")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Select "Blue Bottle" from saved cafes
|
||||||
|
// This triggers data-on:change which sets $_step = 2
|
||||||
|
select_searchable(&session.driver, "saved_cafe_id", "Blue Bottle")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Step 2: Select an existing roast
|
||||||
|
// Wait for step 2 content to become visible
|
||||||
|
wait_for_visible(&session.driver, "searchable-select[name='roast_id']")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Select "Test Roast" from existing roasts
|
||||||
|
// This triggers data-on:change which sets $_step = 3
|
||||||
|
select_searchable(&session.driver, "roast_id", "Test Roast")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Step 3: Submit the check-in
|
||||||
|
// Wait for the submit button to be visible
|
||||||
|
let submit_btn = wait_for_visible(&session.driver, "button[type='submit']")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Verify the review section shows the selected cafe and coffee
|
||||||
|
let body = session.driver.find(By::Css("body")).await.unwrap();
|
||||||
|
let body_text = body.text().await.unwrap();
|
||||||
|
assert!(
|
||||||
|
body_text.contains("Blue Bottle"),
|
||||||
|
"Review should show the selected cafe"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
body_text.contains("Test Roast"),
|
||||||
|
"Review should show the selected roast"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Click the Check In button — Datastar @post → redirect script → /cups/...
|
||||||
|
submit_btn.click().await.unwrap();
|
||||||
|
|
||||||
|
// Should redirect to the cup detail page
|
||||||
|
wait_for_url_contains(&session.driver, "/cups/")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Verify the cup detail page shows the cafe and roast
|
||||||
|
let detail_body = session.driver.find(By::Css("body")).await.unwrap();
|
||||||
|
let detail_text = detail_body.text().await.unwrap();
|
||||||
|
assert!(
|
||||||
|
detail_text.contains("Blue Bottle"),
|
||||||
|
"Cup detail should show the cafe name"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
detail_text.contains("Test Roast"),
|
||||||
|
"Cup detail should show the roast name"
|
||||||
|
);
|
||||||
|
|
||||||
|
session.quit().await;
|
||||||
|
}
|
||||||
49
tests/e2e/delete_tests.rs
Normal file
49
tests/e2e/delete_tests.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
use thirtyfour::prelude::*;
|
||||||
|
|
||||||
|
use crate::helpers::auth::authenticate_browser;
|
||||||
|
use crate::helpers::browser::BrowserSession;
|
||||||
|
use crate::helpers::server_helpers::{create_default_roaster, spawn_app_with_auth};
|
||||||
|
use crate::helpers::wait::{wait_for_url_contains, wait_for_visible};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn delete_roaster_from_detail_page() {
|
||||||
|
let app = spawn_app_with_auth().await;
|
||||||
|
let roaster = create_default_roaster(&app).await;
|
||||||
|
|
||||||
|
let session = BrowserSession::new(&app.address).await.unwrap();
|
||||||
|
authenticate_browser(&session, &app).await.unwrap();
|
||||||
|
|
||||||
|
// Navigate to the roaster detail page
|
||||||
|
session
|
||||||
|
.goto(&format!("/roasters/{}", roaster.slug))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Verify we're on the right page
|
||||||
|
let heading = session.driver.find(By::Css("h1")).await.unwrap();
|
||||||
|
assert_eq!(heading.text().await.unwrap(), "Test Roasters");
|
||||||
|
|
||||||
|
// Click the delete button
|
||||||
|
let delete_btn = wait_for_visible(&session.driver, "button.text-error")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
delete_btn.click().await.unwrap();
|
||||||
|
|
||||||
|
// Accept the confirm() dialog
|
||||||
|
session.driver.accept_alert().await.unwrap();
|
||||||
|
|
||||||
|
// After @delete + redirect script, the browser navigates to /data
|
||||||
|
wait_for_url_contains(&session.driver, "/data")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Verify the roaster is no longer in the list
|
||||||
|
let body = session.driver.find(By::Css("body")).await.unwrap();
|
||||||
|
let body_text = body.text().await.unwrap();
|
||||||
|
assert!(
|
||||||
|
!body_text.contains("Test Roasters"),
|
||||||
|
"Deleted roaster should not appear in the list"
|
||||||
|
);
|
||||||
|
|
||||||
|
session.quit().await;
|
||||||
|
}
|
||||||
96
tests/e2e/roaster_tests.rs
Normal file
96
tests/e2e/roaster_tests.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
use thirtyfour::prelude::*;
|
||||||
|
|
||||||
|
use crate::helpers::auth::authenticate_browser;
|
||||||
|
use crate::helpers::browser::BrowserSession;
|
||||||
|
use crate::helpers::forms::{fill_input, submit_visible_form};
|
||||||
|
use crate::helpers::server_helpers::spawn_app_with_auth;
|
||||||
|
use crate::helpers::wait::{wait_for_url_contains, wait_for_visible};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn create_roaster_via_add_page() {
|
||||||
|
let app = spawn_app_with_auth().await;
|
||||||
|
let session = BrowserSession::new(&app.address).await.unwrap();
|
||||||
|
authenticate_browser(&session, &app).await.unwrap();
|
||||||
|
|
||||||
|
// Navigate to /add — roaster tab is the default
|
||||||
|
session.goto("/add").await.unwrap();
|
||||||
|
|
||||||
|
// Wait for Datastar to initialize and show the roaster form
|
||||||
|
wait_for_visible(&session.driver, "input[name='name']")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Fill the roaster form
|
||||||
|
fill_input(&session.driver, "name", "E2E Test Roasters")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "country", "United Kingdom")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Submit — standard method=post, redirects to detail page
|
||||||
|
submit_visible_form(&session.driver).await.unwrap();
|
||||||
|
|
||||||
|
// Should redirect to the roaster detail page
|
||||||
|
wait_for_url_contains(&session.driver, "/roasters/")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Verify the detail page shows the roaster name
|
||||||
|
let heading = session.driver.find(By::Css("h1")).await.unwrap();
|
||||||
|
let heading_text = heading.text().await.unwrap();
|
||||||
|
assert_eq!(heading_text, "E2E Test Roasters");
|
||||||
|
|
||||||
|
// Navigate to the data page and verify the roaster appears in the list
|
||||||
|
session.goto("/data?type=roasters").await.unwrap();
|
||||||
|
|
||||||
|
let body = session.driver.find(By::Css("body")).await.unwrap();
|
||||||
|
let body_text = body.text().await.unwrap();
|
||||||
|
assert!(
|
||||||
|
body_text.contains("E2E Test Roasters"),
|
||||||
|
"Roaster should appear in the data list"
|
||||||
|
);
|
||||||
|
|
||||||
|
session.quit().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn create_roaster_with_all_fields() {
|
||||||
|
let app = spawn_app_with_auth().await;
|
||||||
|
let session = BrowserSession::new(&app.address).await.unwrap();
|
||||||
|
authenticate_browser(&session, &app).await.unwrap();
|
||||||
|
|
||||||
|
session.goto("/add").await.unwrap();
|
||||||
|
|
||||||
|
wait_for_visible(&session.driver, "input[name='name']")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
fill_input(&session.driver, "name", "Full Detail Roasters")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "country", "Ethiopia")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "city", "Addis Ababa")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
fill_input(&session.driver, "homepage", "https://example.coffee")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
submit_visible_form(&session.driver).await.unwrap();
|
||||||
|
|
||||||
|
wait_for_url_contains(&session.driver, "/roasters/")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Verify detail page shows country and city
|
||||||
|
let body = session.driver.find(By::Css("body")).await.unwrap();
|
||||||
|
let body_text = body.text().await.unwrap();
|
||||||
|
assert!(body_text.contains("Full Detail Roasters"));
|
||||||
|
assert!(body_text.contains("Ethiopia"));
|
||||||
|
assert!(body_text.contains("Addis Ababa"));
|
||||||
|
|
||||||
|
session.quit().await;
|
||||||
|
}
|
||||||
111
tests/e2e/scan_tests.rs
Normal file
111
tests/e2e/scan_tests.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
use thirtyfour::prelude::*;
|
||||||
|
use wiremock::matchers::{method, path};
|
||||||
|
use wiremock::{Mock, ResponseTemplate};
|
||||||
|
|
||||||
|
use crate::helpers::auth::authenticate_browser;
|
||||||
|
use crate::helpers::browser::BrowserSession;
|
||||||
|
use crate::helpers::server_helpers::spawn_app_with_openrouter_mock;
|
||||||
|
use crate::helpers::wait::{wait_for_url_contains, wait_for_visible};
|
||||||
|
|
||||||
|
fn mock_openrouter_response(json_content: &str) -> ResponseTemplate {
|
||||||
|
let body = serde_json::json!({
|
||||||
|
"id": "gen-test",
|
||||||
|
"model": "test-model",
|
||||||
|
"choices": [{
|
||||||
|
"index": 0,
|
||||||
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": json_content
|
||||||
|
},
|
||||||
|
"finish_reason": "stop"
|
||||||
|
}],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 100,
|
||||||
|
"completion_tokens": 50,
|
||||||
|
"total_tokens": 150,
|
||||||
|
"cost": 0.001
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ResponseTemplate::new(200).set_body_json(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn extract_roaster_via_text_prompt_populates_form() {
|
||||||
|
let app = spawn_app_with_openrouter_mock().await;
|
||||||
|
let mock_server = app.mock_server.as_ref().unwrap();
|
||||||
|
|
||||||
|
// Mount mock that returns extracted roaster data
|
||||||
|
Mock::given(method("POST"))
|
||||||
|
.and(path("/api/v1/chat/completions"))
|
||||||
|
.respond_with(mock_openrouter_response(
|
||||||
|
r#"{"name": "Square Mile", "country": "United Kingdom", "city": "London", "homepage": "https://squaremilecoffee.com"}"#,
|
||||||
|
))
|
||||||
|
.mount(mock_server)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let session = BrowserSession::new(&app.address).await.unwrap();
|
||||||
|
authenticate_browser(&session, &app).await.unwrap();
|
||||||
|
|
||||||
|
// Navigate to /add (roaster tab is default)
|
||||||
|
session.goto("/add").await.unwrap();
|
||||||
|
|
||||||
|
// Wait for the extraction prompt to be visible
|
||||||
|
let prompt_input = wait_for_visible(
|
||||||
|
&session.driver,
|
||||||
|
"input[name='prompt'][form='roaster-extract-form']",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Type a description and press Enter to submit the extraction form
|
||||||
|
prompt_input.send_keys("Square Mile Coffee").await.unwrap();
|
||||||
|
prompt_input.send_keys(Key::Enter).await.unwrap();
|
||||||
|
|
||||||
|
// Wait for extraction to complete — the name field should be populated
|
||||||
|
// Datastar patches signals which update form fields via data-bind
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||||
|
|
||||||
|
// Verify the main form fields were populated by the signal patch
|
||||||
|
let name_input = session
|
||||||
|
.driver
|
||||||
|
.find(By::Css(
|
||||||
|
"form[action='/api/v1/roasters'] input[name='name']",
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let name_value = name_input.value().await.unwrap().unwrap_or_default();
|
||||||
|
assert_eq!(name_value, "Square Mile", "Name field should be populated");
|
||||||
|
|
||||||
|
let country_input = session
|
||||||
|
.driver
|
||||||
|
.find(By::Css(
|
||||||
|
"form[action='/api/v1/roasters'] input[name='country']",
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let country_value = country_input.value().await.unwrap().unwrap_or_default();
|
||||||
|
assert_eq!(
|
||||||
|
country_value, "United Kingdom",
|
||||||
|
"Country field should be populated"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Submit the main form to save the roaster
|
||||||
|
let submit_button = session
|
||||||
|
.driver
|
||||||
|
.find(By::Css(
|
||||||
|
"form[action='/api/v1/roasters'] button[type='submit']",
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
submit_button.click().await.unwrap();
|
||||||
|
|
||||||
|
// Should redirect to the roaster detail page
|
||||||
|
wait_for_url_contains(&session.driver, "/roasters/")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let heading = session.driver.find(By::Css("h1")).await.unwrap();
|
||||||
|
assert_eq!(heading.text().await.unwrap(), "Square Mile");
|
||||||
|
|
||||||
|
session.quit().await;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue