У меня есть фрагмент кода, который отключит форму при запуске процесса 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();
}
);
Можете ли вы поделиться полной страницей и кодом тестирования в качестве минимально воспроизводимого примера? Возможно, вы захотите начать прослушивать отключенное условие перед нажатием, в противном случае я предполагаю, что отправка формы происходит настолько быстро, что у вас не будет возможности ее уловить.
После обновления 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?