I have a bit of code that would disable a form when I start the submit
process. This is an classic web app not a Single Page app so the submit button will actually navigate out of the page. The following is an example HTML of how I implemented this.
<!DOCTYPE html>
<html lang="en">
<head>
<title>MVCE</title>
<script>
function disableForm(theForm) {
theForm.querySelectorAll("input, textarea, select").forEach(
/** @param {HTMLInputElement} element */
(element) => {
element.readOnly = true;
}
);
theForm
.querySelectorAll("input[type='submit'], input[type='button'], button")
.forEach(
/** @param {HTMLButtonElement} element */
(element) => {
element.disabled = true;
}
);
}
document.addEventListener('DOMContentLoaded',
function applyDisableFormOnSubmit(formId) {
document.querySelectorAll("form").forEach((aForm) => {
aForm.addEventListener("submit", (event) => {
event.preventDefault();
disableForm(aForm);
aForm.submit();
});
});
});
</script>
</head>
<body>
<form class="generated-form" id="x" method="POST" action="https://httpbun.com/mix/s=200/d=3/b64=dGVzdA==">
<fieldset>
<legend> Student:: </legend>
<label for="fname">First name:</label><br>
<input type="text" id="fname" name="fname" value="John"><br>
<label for="lname">Last name:</label><br>
<input type="text" id="lname" name="lname" value="Doe"><br>
<label for="email">Email:</label><br>
<input type="email" id="email" name="email" value="[email protected]"><br><br>
<input type="submit" value="Submit">
</fieldset>
</form>
</body>
</html>
I wanted to test the above code is functioning correctly in Playwright, but I cannot get the isDisabled
assertion to work in the following code if I uncomment the assertion.
test("disable form", async ({ page }) => {
await page.goto("http://localhost:8080/");
const submitButton = page.locator("input[type='submit']");
await expect(submitButton).toBeEnabled();
await submitButton.click();
// await expect(submitButton).toBeDisabled();
await expect(page)
.toHaveURL("https://httpbun.com/mix/s=200/d=3/b64=dGVzdA==");
});
The following have been tried
test("disable form", async ({ page }) => {
await page.goto("http://localhost:8080/");
const submitButton = page.locator("input[type='submit']");
await expect(submitButton).toBeEnabled();
await Promise.all([
submitButton.click(),
expect(submitButton).toBeDisabled()
]);
expect(page).toHaveURL("https://httpbun.com/mix/s=200/d=3/b64=dGVzdA==");
});
This also does not work, the example in the answer provided appears to be working until you actually try to verify the following page.
test("disable form", async ({ page }) => {
await page.goto("http://localhost:8080/");
const submitButton = page.locator("input[type='submit']");
await expect(submitButton).toBeEnabled();
const nav = page.waitForNavigation({timeout: 5000});
const disabled = expect(submitButton).toBeDisabled({timeout: 1000});
await submitButton.click();
// allow disabled check to pass if the nav happens
// too fast and the button disabled state doesn't render
await disabled.catch(() => {});
await nav;
expect(page).toHaveURL("https://httpbun.com/mix/s=200/d=3/b64=dGVzdA==");
});
2
Answers
At present Playwright does not appear to support this capability as per
per https://playwright.dev/docs/navigations#navigation-events
Once navigation starts the elements are no longer accessible until the page is loaded even with
noWaitAfter: true
Opened this feature request to hopefully solve the issue https://github.com/microsoft/playwright/issues/31596
Assuming the submission is fast, I’d try listening for the disabled condition first, then clicking, so you won’t miss the brief state that occurs only for an instant before page refresh:
If the page refresh happens instantly, the assertion will fail because the page refresh caused by the
.submit()
call will occur before the page gets a chance to render the disabled form state. Assuming it pertains accurately enough to your use case, you can play with the following example to see the behavior:So if the submission is fast, you may not be able to accurately test this, short of instrumenting the source page a bit.
An alternative idea is to make the
expect(submitButton).toBeDisabled()
race against a navigation expectation. If the nav expectation runs first and the disabled check never gets a chance to run, then consider that test a pass too.Something like:
One downside of this approach is that if the nav beats the disabled state, which it would under normal circumstances without the timeout, then your test will incur the 1 second penalty. Not terrible, but a bit slower than it should be. You can lower the timeout at the risk of false positives.
If this doesn’t work, please share a complete, reproducible example with the page under test so I can see the issue first hand and experiment.