为何测试无法检测到表单字段非空时登录按钮启用状态?
登录按钮启用测试失败排查与修复
问题描述
我实现了一个登录页面,规则是当emailId和password字段为空时保持按钮禁用,两个字段都填入文本后启用按钮。但运行测试时出现错误提示Received element is not enabled,求指导问题出在哪里。
代码展示
Login.tsx
const Login = () => { const navigate = useNavigate(); const [error, setError] = useState(""); const [emailId, setEmailId] = useState(""); const [password, setPassword] = useState(""); const [loading, setLoading] = useState(false); return ( <> <NavBar variant="secondary" button={<SignupBtn />} /> <Container> <MuiTextField fullWidth label="Email address" id="outlined-size-small" size="small" onChange={(event) => { setEmailId(event.target.value); setError(""); }} /> <MuiTextField fullWidth label="Password" id="outlined-size-small" size="small" onChange={(event) => { setPassword(event.target.value); setError(""); }} /> {error && ( <Alert variant="outlined" severity="error"> {error} </Alert> )} <Button variant="contained" disabled={!emailId || !password} onClick={(e) => handleLogin({ e, navigate, data: { emailId, password }, setError, setLoading })}> {loading ? <Progress size={20} /> : "Login"} </Button> </Container> </> ); };
测试代码
test("button enables when form fields are not empty", async () => { render(<Login />, { wrapper: BrowserRouter }); const user = userEvent.setup(); const emailTextField = screen.getByLabelText(/email address/i); const passwordTextField = screen.getByLabelText(/password/i); const LoginBtn = screen.getByRole("button", { name: "Login" }); await waitFor(async () => { await user.type(emailTextField, "hello"); await user.type(passwordTextField, "12345"); expect(LoginBtn).toBeEnabled(); }); });
错误信息
Received element is not enabled: expect(LoginBtn).toBeEnabled(); ^
问题原因
- 输入框未绑定value属性:这是核心问题。你的
MuiTextField没有将emailId和password状态绑定到value属性,属于非受控组件写法,但你又依赖state判断按钮禁用状态,导致输入时state更新和输入框实际值不同步,React无法正确触发重渲染更新按钮状态。 - 按钮元素引用过时:测试一开始就获取了
LoginBtn元素,但React状态更新后按钮会重新渲染,之前获取的元素还是旧的禁用状态引用,没有同步最新状态。 - waitFor使用逻辑错误:在waitFor内部同时执行输入操作和断言,输入操作是异步的,应该先完成输入再等待状态更新断言。
修复方案
第一步:修复输入框的受控绑定
修改Login.tsx中的两个MuiTextField,添加value属性绑定对应state:
// 邮箱输入框 <MuiTextField fullWidth label="Email address" id="outlined-size-small" size="small" value={emailId} // 新增:绑定state到输入框value onChange={(event) => { setEmailId(event.target.value); setError(""); }} /> // 密码输入框 <MuiTextField fullWidth label="Password" id="outlined-size-small" size="small" value={password} // 新增:绑定state到输入框value onChange={(event) => { setPassword(event.target.value); setError(""); }} />
第二步:修改测试代码,获取最新按钮元素
调整测试逻辑,输入完成后重新获取按钮元素,或者在waitFor中直接获取最新元素断言:
方案一:输入完成后重新获取按钮
test("button enables when form fields are not empty", async () => { render(<Login />, { wrapper: BrowserRouter }); const user = userEvent.setup(); const emailTextField = screen.getByLabelText(/email address/i); const passwordTextField = screen.getByLabelText(/password/i); // 完成输入操作 await user.type(emailTextField, "hello"); await user.type(passwordTextField, "12345"); // 输入完成后获取最新的按钮元素 const loginBtn = screen.getByRole("button", { name: "Login" }); expect(loginBtn).toBeEnabled(); });
方案二:用waitFor等待按钮状态更新
test("button enables when form fields are not empty", async () => { render(<Login />, { wrapper: BrowserRouter }); const user = userEvent.setup(); const emailTextField = screen.getByLabelText(/email address/i); const passwordTextField = screen.getByLabelText(/password/i); await user.type(emailTextField, "hello"); await user.type(passwordTextField, "12345"); // 等待按钮变为可用状态,同时获取最新元素 await waitFor(() => { expect(screen.getByRole("button", { name: "Login" })).toBeEnabled(); }); });
内容的提问来源于stack exchange,提问作者Pawan
相关产品推荐
相关产品推荐

