Working on LoginPage tests

This commit is contained in:
2025-10-26 22:45:34 +01:00
parent b98e52378e
commit 09d012d065
7 changed files with 900 additions and 320 deletions

View File

@@ -1,8 +1,10 @@
import os
import pytest
from fasthtml.fastapp import fast_app
from myfasthtml.auth.routes import setup_auth_routes
from myfasthtml.auth.utils import create_auth_beforeware
from myfasthtml.auth.utils import create_auth_beforeware, register_user
from myfasthtml.core.testclient import MyTestClient
@@ -10,7 +12,7 @@ from myfasthtml.core.testclient import MyTestClient
def app():
beforeware = create_auth_beforeware()
test_app, test_rt = fast_app(before=beforeware)
setup_auth_routes(test_app, test_rt)
setup_auth_routes(test_app, test_rt, mount_auth_app=True, sqlite_db_path="TestUsers.db")
return test_app
@@ -24,16 +26,34 @@ def user(app):
user = MyTestClient(app)
return user
@pytest.fixture(autouse=True)
def cleanup():
if os.path.exists("TestUsers.db"):
os.remove("TestUsers.db")
def test_i_can_see_login_page(user):
user.open("/login")
user.should_see("Sign In")
user.should_see("Register here")
user.find_form(fields=["Email", "Password"])
def test_i_cannot_login_with_wrong_credentials(user):
user.open("/login")
user.fill_form({
"username": "wrong",
"password": ""
})
user.click("button")
user.should_see("Invalid credentials")
form = user.find_form(fields=["Email", "Password"])
form.fill(Email="user@email.com", Password="#Passw0rd")
form.submit()
user.should_see("Invalid email or password. Please try again.")
def test_i_can_login_with_correct_credentials(user):
# create user
register_user("user@email.com", "user", "#Passw0rd")
user.open("/login")
form = user.find_form(fields=["Email", "Password"])
form.fill(Email="user@email.com", Password="#Passw0rd")
form.submit()
user.should_see("You are now logged in")

View File

@@ -2,43 +2,45 @@ import pytest
from fasthtml.components import Div
from fasthtml.fastapp import fast_app
from myfasthtml.core.testclient import MyTestClient, TestableElement
from myfasthtml.core.testclient import MyTestClient, TestableElement, TestableForm
def test_i_can_open_a_page():
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
class TestMyTestClientOpen:
@rt('/')
def get(): return "hello world"
def test_i_can_open_a_page(self):
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get(): return "hello world"
client.open("/")
assert client.get_content() == "hello world"
client.open("/")
def test_i_can_open_a_page_when_html(self):
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get(): return Div("hello world")
client.open("/")
assert client.get_content() == ' <!doctype html>\n <html>\n <head>\n <title>FastHTML page</title>\n <link rel="canonical" href="http://testserver/">\n </head>\n <body>\n <div>hello world</div>\n </body>\n </html>\n'
assert client.get_content() == "hello world"
def test_i_cannot_open_a_page_not_defined(self):
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
with pytest.raises(AssertionError) as exc_info:
client.open("/not_found")
assert str(exc_info.value) == "Failed to open '/not_found'. status code=404 : reason='404 Not Found'"
def test_i_can_open_a_page_when_html():
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get(): return Div("hello world")
client.open("/")
assert client.get_content() == ' <!doctype html>\n <html>\n <head>\n <title>FastHTML page</title>\n <link rel="canonical" href="http://testserver/">\n </head>\n <body>\n <div>hello world</div>\n </body>\n </html>\n'
def test_i_cannot_open_a_page_not_defined():
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
with pytest.raises(AssertionError) as exc_info:
client.open("/not_found")
assert str(exc_info.value) == "Failed to open '/not_found'. status code=404 : reason='404 Not Found'"
def test_i_can_see_text_in_plain_response():
class TestMyTestClientShouldSee:
def test_i_can_see_text_in_plain_response(self):
"""Test that should_see() works with plain text responses."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@@ -48,279 +50,435 @@ def test_i_can_see_text_in_plain_response():
return "hello world"
client.open("/").should_see("hello world")
def test_i_can_see_text_in_html_response():
"""Test that should_see() extracts visible text from HTML responses."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return "<html><body><h1>Welcome</h1><p>This is a test</p></body></html>"
def test_i_can_see_text_in_html_response(self):
"""Test that should_see() extracts visible text from HTML responses."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return "<html><body><h1>Welcome</h1><p>This is a test</p></body></html>"
client.open("/").should_see("Welcome").should_see("This is a test")
client.open("/").should_see("Welcome").should_see("This is a test")
def test_i_can_see_text_ignoring_html_tags():
"""Test that should_see() searches in visible text only, not in HTML tags."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
def test_i_can_see_text_ignoring_html_tags(self):
"""Test that should_see() searches in visible text only, not in HTML tags."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '<div class="container">Content</div>'
# Should find the visible text
client.open("/").should_see("Content")
# Should NOT find text that's only in attributes/tags
with pytest.raises(AssertionError) as exc_info:
client.should_see("container")
assert "Expected to see 'container' in page content but it was not found" in str(exc_info.value)
@rt('/')
def get():
return '<div class="container">Content</div>'
def test_i_cannot_see_text_that_is_not_present(self):
"""Test that should_see() raises AssertionError when text is not found."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return "hello world"
with pytest.raises(AssertionError) as exc_info:
client.open("/").should_see("goodbye")
assert "Expected to see 'goodbye' in page content but it was not found" in str(exc_info.value)
assert "hello world" in str(exc_info.value)
# Should find the visible text
client.open("/").should_see("Content")
def test_i_cannot_call_should_see_without_opening_page(self):
"""Test that should_see() raises ValueError if no page has been opened."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
with pytest.raises(ValueError) as exc_info:
client.should_see("anything")
assert str(exc_info.value) == "No page content available. Call open() before should_see()."
# Should NOT find text that's only in attributes/tags
with pytest.raises(AssertionError) as exc_info:
client.should_see("container")
def test_i_can_verify_text_is_not_present(self):
"""Test that should_not_see() works when text is absent."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return "hello world"
client.open("/").should_not_see("goodbye")
assert "Expected to see 'container' in page content but it was not found" in str(exc_info.value)
def test_i_cannot_see_text_that_is_not_present():
"""Test that should_see() raises AssertionError when text is not found."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
def test_i_cannot_use_should_not_see_when_text_is_present(self):
"""Test that should_not_see() raises AssertionError when text is found."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return "hello world"
with pytest.raises(AssertionError) as exc_info:
client.open("/").should_not_see("hello")
error_message = str(exc_info.value)
assert "Expected NOT to see 'hello' in page content but it was found" in error_message
@rt('/')
def get():
return "hello world"
def test_i_cannot_call_should_not_see_without_opening_page(self):
"""Test that should_not_see() raises ValueError if no page has been opened."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
with pytest.raises(ValueError) as exc_info:
client.should_not_see("anything")
assert str(exc_info.value) == "No page content available. Call open() before should_not_see()."
with pytest.raises(AssertionError) as exc_info:
client.open("/").should_see("goodbye")
def test_i_can_chain_multiple_assertions(self):
"""Test that assertions can be chained together."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return "<html><body><h1>Welcome</h1><p>Content here</p></body></html>"
# Chain multiple assertions
client.open("/").should_see("Welcome").should_see("Content").should_not_see("Error")
assert "Expected to see 'goodbye' in page content but it was not found" in str(exc_info.value)
assert "hello world" in str(exc_info.value)
def test_i_cannot_call_should_see_without_opening_page():
"""Test that should_see() raises ValueError if no page has been opened."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
def test_i_can_see_element_context_when_text_should_not_be_seen(self):
"""Test that the HTML element containing the text is displayed with parent context."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '<div class="container"><p class="content">forbidden text</p></div>'
with pytest.raises(AssertionError) as exc_info:
client.open("/").should_not_see("forbidden text")
error_message = str(exc_info.value)
assert "Found in:" in error_message
assert '<p class="content">forbidden text</p>' in error_message
assert '<div class="container">' in error_message
with pytest.raises(ValueError) as exc_info:
client.should_see("anything")
def test_i_can_configure_parent_levels_in_constructor(self):
"""Test that parent_levels parameter controls the number of parent levels shown."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app, parent_levels=2)
@rt('/')
def get():
return '<body><div class="wrapper"><div class="container"><p>error</p></div></div></body>'
with pytest.raises(AssertionError) as exc_info:
client.open("/").should_not_see("error")
error_message = str(exc_info.value)
assert '<p>error</p>' in error_message
assert '<div class="container">' in error_message
assert '<div class="wrapper">' in error_message
assert str(exc_info.value) == "No page content available. Call open() before should_see()."
def test_i_can_verify_text_is_not_present():
"""Test that should_not_see() works when text is absent."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
def test_i_can_find_text_in_nested_elements(self):
"""Test that the smallest element containing the text is found in nested structures."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '<div><section><article><p class="target">nested text</p></article></section></div>'
with pytest.raises(AssertionError) as exc_info:
client.open("/").should_not_see("nested text")
error_message = str(exc_info.value)
# Should find the <p> element, not the outer <div>
assert '<p class="target">nested text</p>' in error_message
@rt('/')
def get():
return "hello world"
def test_i_can_find_fragmented_text_across_tags(self):
"""Test that text fragmented across multiple tags is correctly found."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '<p class="message">hel<span>lo</span> world</p>'
with pytest.raises(AssertionError) as exc_info:
client.open("/").should_not_see("hello world")
error_message = str(exc_info.value)
# Should find the parent <p> element that contains the full text
assert '<p class="message">' in error_message
client.open("/").should_not_see("goodbye")
def test_i_cannot_use_should_not_see_when_text_is_present():
"""Test that should_not_see() raises AssertionError when text is found."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return "hello world"
with pytest.raises(AssertionError) as exc_info:
client.open("/").should_not_see("hello")
error_message = str(exc_info.value)
assert "Expected NOT to see 'hello' in page content but it was found" in error_message
def test_i_cannot_call_should_not_see_without_opening_page():
"""Test that should_not_see() raises ValueError if no page has been opened."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
with pytest.raises(ValueError) as exc_info:
client.should_not_see("anything")
assert str(exc_info.value) == "No page content available. Call open() before should_not_see()."
def test_i_can_chain_multiple_assertions():
"""Test that assertions can be chained together."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return "<html><body><h1>Welcome</h1><p>Content here</p></body></html>"
# Chain multiple assertions
client.open("/").should_see("Welcome").should_see("Content").should_not_see("Error")
def test_i_can_see_element_context_when_text_should_not_be_seen():
"""Test that the HTML element containing the text is displayed with parent context."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '<div class="container"><p class="content">forbidden text</p></div>'
with pytest.raises(AssertionError) as exc_info:
client.open("/").should_not_see("forbidden text")
error_message = str(exc_info.value)
assert "Found in:" in error_message
assert '<p class="content">forbidden text</p>' in error_message
assert '<div class="container">' in error_message
def test_i_can_configure_parent_levels_in_constructor():
"""Test that parent_levels parameter controls the number of parent levels shown."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app, parent_levels=2)
@rt('/')
def get():
return '<body><div class="wrapper"><div class="container"><p>error</p></div></div></body>'
with pytest.raises(AssertionError) as exc_info:
def test_i_do_not_find_text_in_html_attributes(self):
"""Test that text in HTML attributes is not considered as visible text."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '<div class="error message" title="error info">Success</div>'
# "error" is in attributes but not in visible text
client.open("/").should_not_see("error")
# "Success" is in visible text
with pytest.raises(AssertionError):
client.should_not_see("Success")
class TestMyTestClientFindElement:
error_message = str(exc_info.value)
assert '<p>error</p>' in error_message
assert '<div class="container">' in error_message
assert '<div class="wrapper">' in error_message
def test_i_can_find_text_in_nested_elements():
"""Test that the smallest element containing the text is found in nested structures."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '<div><section><article><p class="target">nested text</p></article></section></div>'
with pytest.raises(AssertionError) as exc_info:
client.open("/").should_not_see("nested text")
error_message = str(exc_info.value)
# Should find the <p> element, not the outer <div>
assert '<p class="target">nested text</p>' in error_message
def test_i_can_find_fragmented_text_across_tags():
"""Test that text fragmented across multiple tags is correctly found."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '<p class="message">hel<span>lo</span> world</p>'
with pytest.raises(AssertionError) as exc_info:
client.open("/").should_not_see("hello world")
error_message = str(exc_info.value)
# Should find the parent <p> element that contains the full text
assert '<p class="message">' in error_message
def test_i_do_not_find_text_in_html_attributes():
"""Test that text in HTML attributes is not considered as visible text."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '<div class="error message" title="error info">Success</div>'
# "error" is in attributes but not in visible text
client.open("/").should_not_see("error")
# "Success" is in visible text
with pytest.raises(AssertionError):
client.should_not_see("Success")
@pytest.mark.parametrize("selector,expected_tag", [
("#unique-id", '<div class="main wrapper"'),
(".home-link", '<a class="link home-link"'),
("div > div", '<div class="content"'),
("div span", "<span"),
("[data-type]", '<a class="link home-link"'),
('[data-type="navigation"]', '<a class="link home-link"'),
('[class~="link"]', '<a class="link home-link"'),
('[href^="/home"]', '<a class="link home-link"'),
('[href$="about"]', '<a href="/about">'),
('[data-author*="john"]', '<a class="link home-link"'),
])
def test_i_can_find_element(selector, expected_tag):
"""Test that find_element works with various CSS selectors."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '''
<div id="unique-id" class="main wrapper">
<a href="/home" class="link home-link" data-type="navigation" data-author="john-doe">Home</a>
<a href="/about">About</a>
<div class="content">
<span class="text">Content</span>
@pytest.mark.parametrize("selector,expected_tag", [
("#unique-id", '<div class="main wrapper"'),
(".home-link", '<a class="link home-link"'),
("div > div", '<div class="content"'),
("div span", "<span"),
("[data-type]", '<a class="link home-link"'),
('[data-type="navigation"]', '<a class="link home-link"'),
('[class~="link"]', '<a class="link home-link"'),
('[href^="/home"]', '<a class="link home-link"'),
('[href$="about"]', '<a href="/about">'),
('[data-author*="john"]', '<a class="link home-link"'),
])
def test_i_can_find_element(self, selector, expected_tag):
"""Test that find_element works with various CSS selectors."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '''
<div id="unique-id" class="main wrapper">
<a href="/home" class="link home-link" data-type="navigation" data-author="john-doe">Home</a>
<a href="/about">About</a>
<div class="content">
<span class="text">Content</span>
</div>
</div>
</div>
'''
element = client.open("/").find_element(selector)
assert element is not None
assert isinstance(element, TestableElement)
assert expected_tag in element.html_fragment
'''
element = client.open("/").find_element(selector)
assert element is not None
assert isinstance(element, TestableElement)
assert expected_tag in element.html_fragment
def test_i_cannot_find_element_when_none_exists(self):
"""Test that find_element raises AssertionError when no element matches."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '<div class="container"><p>Content</p></div>'
with pytest.raises(AssertionError) as exc_info:
client.open("/").find_element("#non-existent")
assert "No element found matching selector '#non-existent'" in str(exc_info.value)
def test_i_cannot_find_element_when_multiple_exist(self):
"""Test that find_element raises AssertionError when multiple elements match."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '<div><p class="text">First</p><p class="text">Second</p><p class="text">Third</p></div>'
with pytest.raises(AssertionError) as exc_info:
client.open("/").find_element(".text")
error_message = str(exc_info.value)
assert "Found 3 elements matching selector '.text'" in error_message
assert "Expected exactly 1" in error_message
def test_i_cannot_call_find_element_without_opening_page(self):
"""Test that find_element raises ValueError if no page has been opened."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
with pytest.raises(ValueError) as exc_info:
client.find_element("#any-selector")
assert str(exc_info.value) == "No page content available. Call open() before find_element()."
def test_i_cannot_find_element_when_none_exists():
"""Test that find_element raises AssertionError when no element matches."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
class TestMyTestClientFindForm:
def test_i_can_find_form(self):
"""Test that find_form works fo simple form."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '''<form></form>'''
form = client.open("/").find_form()
assert form is not None
assert isinstance(form, TestableForm)
def test_i_can_find_form_in_nested_elements(self):
"""Test that find_form works when form is nested in other elements."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '''
<div class="container">
<div class="wrapper">
<form></form>
</div>
</div>
'''
form = client.open("/").find_form()
assert form is not None
assert isinstance(form, TestableForm)
def test_i_can_find_form_with_all_fields(self):
"""Test that find_form works when form has fields."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '''
<form>
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<button type="submit">Sign In</button>
</form>
'''
form = client.open("/").find_form(fields=["username", "password"])
assert form is not None
assert isinstance(form, TestableForm)
def test_i_can_find_form_with_label(self):
"""Test that find_form works when form has fields."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '''
<form>
<label for="username">Username</label>
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<button type="submit">Sign In</button>
</form>
'''
form = client.open("/").find_form(fields=["Username", "password"])
assert form is not None
assert isinstance(form, TestableForm)
def test_i_can_find_form_with_one_field(self):
"""Test that find_form works when form has at least one field."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '''
<form>
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<button type="submit">Sign In</button>
</form>
'''
form = client.open("/").find_form(fields=["username"])
assert form is not None
assert isinstance(form, TestableForm)
def test_i_cannot_find_element_when_none_exists(self):
"""Test that find_form raises AssertionError when no form found."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '<div class="container"><p>Content</p></div>'
with pytest.raises(AssertionError) as exc_info:
client.open("/").find_form()
assert "No form found" in str(exc_info.value)
def test_i_cannot_call_find_form_without_opening_page(self):
"""Test that find_form raises ValueError if no page has been opened."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
with pytest.raises(ValueError) as exc_info:
client.find_form()
assert str(exc_info.value) == "No page content available. Call open() before find_form()."
def test_cannot_find_form_when_multiple_exist(self):
"""Test that find_form raises AssertionError when multiple forms match."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '''
<div class="container">
<form></form>
<form></form>
</div>
'''
with pytest.raises(AssertionError) as exc_info:
client.open("/").find_form()
error_message = str(exc_info.value)
assert "Found 2 forms (with the specified fields). Expected exactly 1." in error_message
def test_cannot_find_form_with_fields_when_multiple_exist(self):
"""Test that find_form raises AssertionError when multiple forms match."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '''
<div class="container">
<form>
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<button type="submit">Sign In</button>
</form>
<form>
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<button type="submit">Sign In</button>
</form>
</div>
'''
with pytest.raises(AssertionError) as exc_info:
client.open("/").find_form(fields=["username", "password"])
error_message = str(exc_info.value)
assert "Found 2 forms (with the specified fields). Expected exactly 1." in error_message
@rt('/')
def get():
return '<div class="container"><p>Content</p></div>'
with pytest.raises(AssertionError) as exc_info:
client.open("/").find_element("#non-existent")
assert "No element found matching selector '#non-existent'" in str(exc_info.value)
def test_i_cannot_find_element_when_multiple_exist():
"""Test that find_element raises AssertionError when multiple elements match."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
@rt('/')
def get():
return '<div><p class="text">First</p><p class="text">Second</p><p class="text">Third</p></div>'
with pytest.raises(AssertionError) as exc_info:
client.open("/").find_element(".text")
error_message = str(exc_info.value)
assert "Found 3 elements matching selector '.text'" in error_message
assert "Expected exactly 1" in error_message
def test_i_cannot_call_find_element_without_opening_page():
"""Test that find_element raises ValueError if no page has been opened."""
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
with pytest.raises(ValueError) as exc_info:
client.find_element("#any-selector")
assert str(exc_info.value) == "No page content available. Call open() before find_element()."

View File

@@ -1,6 +1,7 @@
import pytest
from fasthtml.fastapp import fast_app
from myfasthtml.core.testclient import TestableForm
from myfasthtml.core.testclient import TestableForm, MyTestClient
@pytest.fixture
@@ -583,3 +584,339 @@ class TestableFormUpdateFieldValues:
f"Expected {{'flag': True}}, got {form.fields}"
assert isinstance(form.fields["flag"], bool), \
f"Expected bool type, got {type(form.fields['flag'])}"
class TestMyTestClientFill:
def test_i_can_fill_form_using_input_name(self, mock_client):
"""
I can fill using the input name
"""
html = '<form><label for="uid">Username</label><input id="uid" name="username" /></form>'
form = TestableForm(mock_client, html)
form.fill(username="john_doe")
assert form.fields == {"username": "john_doe"}
def test_i_can_fill_form_using_label(self, mock_client):
"""
I can fill using the label associated with the input
"""
html = '<form><label for="uid">Username</label><input id="uid" name="username" /></form>'
form = TestableForm(mock_client, html)
form.fill(Username="john_doe")
assert form.fields == {"username": "john_doe"}
def test_i_cannot_fill_form_with_invalid_field_name(self, mock_client):
"""
I cannot fill form with invalid field name
"""
html = '<form><label for="uid">Username</label><input id="uid" name="username" /></form>'
form = TestableForm(mock_client, html)
with pytest.raises(ValueError) as excinfo:
form.fill(invalid_field="john_doe")
assert str(excinfo.value) == "Invalid field name 'invalid_field'."
class TestableFormSubmit:
"""
Test suite for the submit() method of TestableForm class.
This module tests form submission for both HTMX and classic forms.
"""
def test_i_can_submit_classic_form_with_post_method(self):
"""
Test that a classic form with POST method is submitted correctly.
This ensures the form uses the action and method attributes properly.
"""
# HTML form with classic submission
html = '''
<form action="/submit" method="post">
<input type="text" name="username" value="john_doe" />
<input type="password" name="password" value="secret123" />
</form>
'''
# Create the form
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
form = TestableForm(client, html)
@rt('/submit')
def post(username: str, password: str):
return f"Form received {username=}, {password=}"
form.submit()
assert client.get_content() == "Form received username='john_doe', password='secret123'"
def test_i_can_submit_classic_form_with_get_method(self):
"""
Test that a classic form with GET method is submitted correctly.
This ensures GET requests are properly handled.
"""
html = '''
<form action="/search" method="get">
<input type="text" name="q" value="python" />
<input type="text" name="page" value="1" />
</form>
'''
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
form = TestableForm(client, html)
@rt('/search')
def get(q: str, page: int):
return f"Search results for {q=}, {page=}"
form.submit()
assert client.get_content() == "Search results for q='python', page=1"
def test_i_can_submit_classic_form_without_method_defaults_to_post(self):
"""
Test that POST is used by default when method attribute is absent.
This ensures proper default behavior matching HTML standards.
"""
html = '''
<form action="/submit">
<input type="text" name="data" value="test" />
</form>
'''
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
form = TestableForm(client, html)
@rt('/submit')
def post(data: str):
return f"Received {data=}"
form.submit()
assert client.get_content() == "Received data='test'"
def test_i_can_submit_form_with_htmx_post(self):
"""
Test that a form with hx_post uses HTMX submission.
This ensures HTMX-enabled forms are handled correctly.
"""
html = '''
<form hx-post="/htmx-submit">
<input type="text" name="username" value="alice" />
</form>
'''
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
form = TestableForm(client, html)
@rt('/htmx-submit')
def post(username: str):
return f"HTMX received {username=}"
form.submit()
assert client.get_content() == "HTMX received username='alice'"
def test_i_can_submit_form_with_htmx_get(self):
"""
Test that a form with hx_get uses HTMX submission.
This ensures HTMX GET requests work properly.
"""
html = '''
<form hx-get="/htmx-search">
<input type="text" name="query" value="fasthtml" />
</form>
'''
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
form = TestableForm(client, html)
@rt('/htmx-search')
def get(query: str):
return f"HTMX search for {query=}"
form.submit()
assert client.get_content() == "HTMX search for query='fasthtml'"
def test_i_can_submit_form_with_filled_fields(self):
"""
Test that fields filled via fill_form() are submitted correctly.
This ensures dynamic form filling works as expected.
"""
html = '''
<form action="/login" method="post">
<input type="text" name="username" value="" />
<input type="password" name="password" value="" />
</form>
'''
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
form = TestableForm(client, html)
# Fill the form dynamically
form.fill(username="bob", password="secure456")
@rt('/login')
def post(username: str, password: str):
return f"Login {username=}, {password=}"
form.submit()
assert client.get_content() == "Login username='bob', password='secure456'"
def test_i_can_submit_form_with_mixed_field_types(self):
"""
Test that all field types are submitted with correct values.
This ensures type conversion and submission work together.
"""
html = '''
<form action="/register" method="post">
<input type="text" name="username" value="charlie" />
<input type="number" name="age" value="30" />
<input type="checkbox" name="newsletter" checked />
<select name="country">
<option value="US" selected>USA</option>
<option value="FR">France</option>
</select>
</form>
'''
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
form = TestableForm(client, html)
@rt('/register')
def post(username: str, age: int, newsletter: bool, country: str):
return f"Registration {username=}, {age=}, {newsletter=}, {country=}"
result = form.submit()
# Note: the types are converted in self.fields
expected = "Registration username='charlie', age=30, newsletter=True, country='US'"
assert client.get_content() == expected
def test_i_cannot_submit_classic_form_without_action(self):
"""
Test that an exception is raised when action attribute is missing.
This ensures proper error handling for malformed forms.
"""
html = '''
<form method="post">
<input type="text" name="data" value="test" />
</form>
'''
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
form = TestableForm(client, html)
# Should raise ValueError
try:
form.submit()
assert False, "Expected ValueError to be raised"
except ValueError as e:
assert "no 'action' attribute" in str(e).lower()
def test_i_cannot_submit_classic_form_with_empty_action(self):
"""
Test that an exception is raised when action attribute is empty.
This ensures validation of the action attribute.
"""
html = '''
<form action="" method="post">
<input type="text" name="data" value="test" />
</form>
'''
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
form = TestableForm(client, html)
# Should raise ValueError
try:
form.submit()
assert False, "Expected ValueError to be raised"
except ValueError as e:
assert "no 'action' attribute" in str(e).lower()
def test_i_can_submit_form_with_case_insensitive_method(self):
"""
Test that HTTP method is properly normalized to uppercase.
This ensures method attribute is case-insensitive.
"""
html = '''
<form action="/submit" method="PoSt">
<input type="text" name="data" value="test" />
</form>
'''
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
form = TestableForm(client, html)
@rt('/submit')
def post(data: str):
return f"Received {data=}"
form.submit()
assert client.get_content() == "Received data='test'"
def test_i_can_prioritize_htmx_over_classic_submission(self):
"""
Test that HTMX is prioritized even when action/method are present.
This ensures correct priority between HTMX and classic submission.
"""
html = '''
<form action="/classic" method="post" hx-post="/htmx">
<input type="text" name="data" value="test" />
</form>
'''
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
form = TestableForm(client, html)
@rt('/classic')
def post_classic(data: str):
return "Classic submission"
@rt('/htmx')
def post_htmx(data: str):
return "HTMX submission"
form.submit()
assert client.get_content() == "HTMX submission"
def test_i_can_submit_empty_form(self):
"""
Test that a form without fields can be submitted.
This ensures robustness for minimal forms.
"""
html = '<form action="/empty" method="post"></form>'
test_app, rt = fast_app(default_hdrs=False)
client = MyTestClient(test_app)
form = TestableForm(client, html)
@rt('/empty')
def post():
return "Empty form received"
form.submit()
assert client.get_content() == "Empty form received"