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==");
});
I have also tried to modify the route
but it still does not work
await page.route(
"https://httpbun.com/mix/s=200/d=3/b64=dGVzdA==",
async (route, req) => {
console.log("routing")
expect(page.url()).toEqual(currentUrl);
await expect(submitButton).toBeVisible();
await expect(submitButton).toBeDisabled();
return route.continue();
}
);
I eventually did a round about way due to the DOM disappearing when navigation starts. What I did was replace the form.submit
with a dummy method to ensure it doesn't navigate and then invoke it afterwards. One thing to note is you cannot set the local variables in the evaluate
so you need to apply it as an property on the context.
type HTMLFormElementWithFlags = HTMLFormElement & {
formSubmitCalled: boolean;
/**
* Stores the form.submit function to be called later
*/
savedSubmit: () => void;
};
/**
*
* @param formLocator form locator
* @returns [ submitTrappedForm, isTrappedFormSubmitCalled ]
*/
const trapFormSubmit = async (
formLocator: Locator
): Promise<[() => Promise<void>, () => Promise<boolean>]> => {
await formLocator.evaluate((elem: HTMLFormElementWithFlags) => {
elem.savedSubmit = elem.submit;
elem.formSubmitCalled = false;
elem.submit = () => {
elem.formSubmitCalled = true;
};
});
return [
async () =>
formLocator.evaluate((elem: HTMLFormElementWithFlags) => {
elem.savedSubmit();
}),
async () =>
formLocator.evaluate(
(elem: HTMLFormElementWithFlags) => elem.formSubmitCalled
),
];
};
test("disable form on submit", async ({ page }) => {
await page.setContent(
`
<!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) => {
console.log("form is submitting");
event.preventDefault();
disableForm(aForm);
aForm.submit();
console.log("submit is called");
});
});
});
</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>
`
);
const submitButton = page.locator("input[type='submit']");
await expect(submitButton).toBeEnabled();
const currentUrl = page.url();
const form = page.locator("form");
const [submitTrappedForm, isTrappedFormSubmitCalled] =
await trapFormSubmit(form);
await submitButton.click();
expect(page.url()).toEqual(currentUrl);
expect(await isTrappedFormSubmitCalled()).toBe(true);
await expect(submitButton).toBeVisible();
await expect(submitButton).toBeDisabled();
await submitTrappedForm();
await page.waitForURL("https://httpbun.com/mix/s=200/d=3/b64=dGVzdA==");
await expect(page).toHaveURL(
"https://httpbun.com/mix/s=200/d=3/b64=dGVzdA=="
);
const content = await page.content();
// Cannot use toEqual because playwright wraps it as an HTML.
expect(content).toContain("test");
});