У меня есть фрагмент кода, который отключит форму при запуске процесса submit. Это классическое веб-приложение, а не одностраничное приложение, поэтому кнопка отправки фактически выходит за пределы страницы. Ниже приведен пример HTML того, как я это реализовал.
<!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>
Я хотел проверить, правильно ли работает приведенный выше код в Playwright, но я не могу заставить утверждение isDisabled работать в следующем коде, если раскомментирую утверждение.
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= = ");
});
Следующие были опробованы
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= = ");
});
Это также не работает: пример в предоставленном ответе, похоже, работает, пока вы на самом деле не попытаетесь проверить следующую страницу.
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= = ");
});
Я также пытался изменить route, но все равно не работает.
await page.route(
"https://httpbun.com/mix/s=200/d=3/b64=dGVzdA= = ",
async (route, req) => {
console.info("routing")
expect(page.url()).toEqual(currentUrl);
await expect(submitButton).toBeVisible();
await expect(submitButton).toBeDisabled();
return route.continue();
}
);
Можете ли вы поделиться полной страницей и кодом тестирования в качестве минимально воспроизводимого примера? Возможно, вы захотите начать прослушивать отключенное условие перед нажатием, в противном случае я предполагаю, что отправка формы происходит настолько быстро, что у вас не будет возможности ее уловить.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


После обновления OP, разъясняющего их вариант использования, в настоящее время это кажется невозможным, поскольку навигация немедленно делает элементы на странице недействительными, что делает невозможным их поиск или подтверждение их состояния.
Я оставлю свой первоначальный ответ ниже для контекста.
Предполагая, что отправка происходит быстро, я бы попробовал сначала прослушать отключенное состояние, а затем щелкнуть, чтобы вы не пропустили краткое состояние, которое возникает только на мгновение перед обновлением страницы:
const submitButton = page.locator("#SubmitButton")
await expect(submitButton).toBeEnabled();
await Promise.all([
expect(submitButton).toBeDisabled(),
submitButton.click(),
]);
Если обновление страницы происходит мгновенно, утверждение не будет выполнено, поскольку обновление страницы, вызванное вызовом .submit(), произойдет до того, как страница получит возможность отобразить отключенное состояние формы. Предполагая, что это достаточно точно относится к вашему варианту использования, вы можете поиграть со следующим примером, чтобы увидеть поведение:
import {expect, test} from "@playwright/test"; // ^1.42.1
const html = `<!DOCTYPE html><html><body>
<form><input id = "SubmitButton" type = "submit"></form>
<script>
const form = document.querySelector("form");
const disableFormFields = (event) => {
event.preventDefault();
form
.querySelectorAll("input, textarea, select")
.forEach((element) => {
element.readOnly = true;
});
form
.querySelectorAll("input[type='submit'], input[type='button'], button")
.forEach((element) => {
element.disabled = true;
});
// Added for experimentation purposes, feel free to adjust or remove
setTimeout(() => form.submit(), 100);
};
form.addEventListener("submit", disableFormFields);
</script></body></html>`;
test("button is disabled during submit", async ({page}) => {
await page.setContent(html);
const submitButton = page.locator("#SubmitButton");
await expect(submitButton).toBeEnabled();
await Promise.all([
expect(submitButton).toBeDisabled(),
submitButton.click(),
]);
});
Поэтому, если отправка выполняется быстро, возможно, вы не сможете точно проверить это, если не считать небольшого инструментирования исходной страницы.
Альтернативная идея — устроить гонку expect(submitButton).toBeDisabled() против ожиданий навигации. Если ожидание навигации выполняется первым, а отключенная проверка не получает возможности выполниться, то считайте, что проверка также пройдена.
Что-то вроде:
test("button is disabled during submit", async ({page}) => {
await page.setContent(html);
const submitButton = page.locator("#SubmitButton");
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;
});
Недостатком этого подхода является то, что если навигация выходит из отключенного состояния, что было бы в обычных обстоятельствах без тайм-аута, то ваш тест повлечет за собой штраф в 1 секунду. Не ужасно, но немного медленнее, чем должно быть. Вы можете уменьшить таймаут из-за риска ложных срабатываний.
Если это не сработает, поделитесь полным, воспроизводимым примером с тестируемой страницей, чтобы я мог увидеть проблему своими глазами и поэкспериментировать.
Кажется, я не могу заставить его работать.
Можете ли вы рассказать об этом подробнее? Что именно не работает? Мой пример работает; Можете ли вы предоставить неудачный пример реальной страницы, чтобы у нас обоих был общий контекст?
Я добавил приведенный выше пример, но не могу заставить «отключено» сработать. Самое сложное — получить доступную для тестирования серверную службу, способную выполнять задержку.
ваш второй пример не проверяет полученный результат, поэтому, хотя он и проверяет «отключено», навигация по сообщениям не проходит.
setTimeout(() => form.submit(), 100); имитирует медленный бэкэнд. Отрегулируйте соответствующим образом. В вашем expect(page).toHaveURL("https://httpbun.com/mix/s=200/d=3/b64=dGVzdA= = "); отсутствует await.
Тем не менее, я вижу await expect(submitButton).toBeDisabled(); неудачу, вероятно, потому, что как только начинается навигация, страница немедленно удаляет свой предыдущий контекст, хотя он все еще отображается на экране. Так что да, я не знаю, как это проверить, но я изучу это подробнее, если у меня будет возможность.
Я пришел к такому же выводу. Тогда откройте это github.com/microsoft/playwright/issues/31596
В настоящее время Playwright, похоже, не поддерживает эту возможность.
за https://playwright.dev/docs/navigations#navigation-events
Навигация начинается с изменения URL-адреса страницы или взаимодействия со страницей (например, щелчка ссылки). Цель навигации может быть отменена, например, при попадании на неразрешенный адрес DNS или преобразована в загрузку файла.
После запуска навигации элементы становятся недоступными до тех пор, пока страница не загрузится даже с помощью noWaitAfter: true.
Открыл этот запрос функции, чтобы, надеюсь, решить проблему https://github.com/microsoft/playwright/issues/31596
В конце концов я сделал обходной путь из-за исчезновения DOM при запуске навигации. Я заменил form.submit фиктивным методом, чтобы гарантировать, что он не перемещается, а затем вызвал его впоследствии. Следует отметить одну вещь: вы не можете установить локальные переменные в evaluate, поэтому вам нужно применить их как свойство в контексте.
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.info("form is submitting");
event.preventDefault();
disableForm(aForm);
aForm.submit();
console.info("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");
});
Почему бы просто не показать наложение с указателем-event: none?