Notes Used to Craft this Lesson
A common browser test you might perform is programmatically submitting a form and confirming that your user feedback and other behaviors are as you expect.
Let's use our login form as an example and test to confirm that our validation works and that old user inputs are applied after an unsuccessful submission.
test("see validation errors after unsuccessful submission", async ({ visit, route, }) => {});Copied!
First, we'll visit our login page.
test("see validation errors after unsuccessful submission", async ({ visit, route, }) => { const page = await visit(route("auth.login.show")); });Copied!
Then, we can use Playwright's APIs to locate elements via selectors.
test("see validation errors after unsuccessful submission", async ({ visit, route, }) => { const page = await visit(route("auth.login.show")); await page.locator("input[type=email]"); });Copied!
Once we have an element selected, we'll have a number of actionable methods at our disposal, like filling the input with a value.
test("see validation errors after unsuccessful submission", async ({ visit, route, }) => { const page = await visit(route("auth.login.show")); await page.locator("input[type=email]").fill("test@test.com"); });Copied!
Great, now let's enter an invalid email address. The email field in the browser is of type email,l so we need something that will pass the browser's rule (so our form actually submits) but fails our validation rule, and "a@b.c" fits the bill just fine!
test("see validation errors after unsuccessful submission", async ({ visit, route, }) => { const page = await visit(route("auth.login.show")); await page.locator("input[type=email]").fill("a@b.c"); });Copied!
Perfect, now this will render our login page and enter "a@b.c" into the email field. Let's enter a password next.
test("see validation errors after unsuccessful submission", async ({ visit, route, }) => { const page = await visit(route("auth.login.show")); await page.locator("input[type=email]").fill("a@b.c"); await page.locator("input[type=password]").fill("something"); });Copied!
Great, now we can go ahead and submit our form. Upon submission, it should hit our server and fail our validation, returning us back to the same page.
test("see validation errors after unsuccessful submission", async ({ visit, route, }) => { const page = await visit(route("auth.login.show")); await page.locator("input[type=email]").fill("a@b.c"); await page.locator("input[type=password]").fill("something"); // locate & click the submit button await page.locator("button[type=submit]").click(); // wait for the page to load after redirecting back // this will specifically wait for the 'DOMContentLoaded' event to fire await page.waitForLoadState("domcontentloaded"); // confirm we were redirected back to the login page await page.assertPath(route("auth.login.show")); });Copied!
Now that we've redirected back and confirmed we're on the right page, we can make some assertions! This should be shown the same as if we'd run through this flow ourselves in the browser, so we should be able to confirm our email validation error is shown.
test("see validation errors after unsuccessful submission", async ({ visit, route, }) => { const page = await visit(route("auth.login.show")); await page.locator("input[type=email]").fill("a@b.c"); await page.locator("input[type=password]").fill("something"); await page.locator("button[type=submit]").click(); await page.waitForLoadState("domcontentloaded"); await page.assertPath(route("auth.login.show")); // assert the id if our email validation error is visible on the page await page.assertVisible("#email-error"); // assert that this id has the following innerText await page.assertText( "#email-error", "The email field must be a valid email address" ); });Copied!
What would be an added plus is to also confirm the user's previously submitted values are still on the form, except for the password. For security purposes, that should always be cleared.
test("see validation errors after unsuccessful submission", async ({ visit, route, }) => { const page = await visit(route("auth.login.show")); await page.locator("input[type=email]").fill("a@b.c"); await page.locator("input[type=password]").fill("something"); await page.locator("button[type=submit]").click(); await page.waitForLoadState("domcontentloaded"); await page.assertPath(route("auth.login.show")); await page.assertVisible("#email-error"); await page.assertText( "#email-error", "The email field must be a valid email address" ); // assert that the email field's value is "a@b.c" await page.assertInputValue("input[type=email]", "a@b.c"); // assert that our password was cleared await page.assertInputValue("input[type=password]", ""); });Copied!
Finally, we can tack our remember me checkbox into this flow as well.
test("see validation errors after unsuccessful submission", async ({ visit, route, }) => { const page = await visit(route("auth.login.show")); await page.locator("input[type=email]").fill("a@b.c"); await page.locator("input[type=password]").fill("something"); // locate and check the remember me checkbox await page.locator("input[name=remember]").setChecked(true); await page.locator("button[type=submit]").click(); await page.waitForLoadState("domcontentloaded"); await page.assertPath(route("auth.login.show")); await page.assertVisible("#email-error"); await page.assertText( "#email-error", "The email field must be a valid email address" ); await page.assertInputValue("input[type=email]", "a@b.c"); await page.assertInputValue("input[type=password]", ""); // assert that it is still checked after invalid submission await page.assertChecked("input[name=remember]"); });Copied!