mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
88 lines
3.7 KiB
JavaScript
88 lines
3.7 KiB
JavaScript
/**
|
|
* Read a message back out of the recipient's mailbox, through the mail
|
|
* provider's webmail.
|
|
*
|
|
* Outbound mail configuration fails in ways the sender never notices: an
|
|
* implicit-TLS port announced as STARTTLS, AUTH offered on a listener that has
|
|
* none, or a relay the application can reach but the provider rejects. In every
|
|
* one of those the send call still returns success. The only proof that a
|
|
* setting is right is the message arriving, so these helpers exist for any role
|
|
* whose configuration decides how mail leaves it.
|
|
*
|
|
* `webmailSsoLogin(page, username, password, baseUrl)`
|
|
* Drives the provider's webmail login. The webmail redirects to Keycloak on
|
|
* its own (`oauth_login_redirect`), so this only has to click an SSO control
|
|
* when one is rendered, then fill the Keycloak form. A caller that already
|
|
* holds a Keycloak session never sees a form — the redirect answers straight
|
|
* back into the mailbox — so the helper waits for whichever of the two
|
|
* surfaces arrives instead of hanging on the form.
|
|
*
|
|
* `waitForEmailInMailbox(page, baseUrl, subject, timeout)`
|
|
* Polls INBOX and Junk for a message whose subject matches, and returns its
|
|
* row. Junk is not a fallback for a broken setup: a test domain publishes no
|
|
* mail-auth DNS, so a correctly delivered message is legitimately filed
|
|
* there. Folders are switched by `_mbox` URL, because clicking a folder link
|
|
* hangs when the row it points at is not actionable yet.
|
|
*/
|
|
|
|
const { performKeycloakLoginForm } = require("./keycloak");
|
|
const { gotoOnion } = require("./env");
|
|
const { resolveTimeout } = require("../../timeouts");
|
|
|
|
const MAIL_UI = "#messagelist, .compose, a[href*='_action=compose'], .toolbar";
|
|
const SSO_CONTROL = /sso|single sign.?on|login with|openid/i;
|
|
|
|
const MAILBOXES = ["INBOX", "Junk Mail"];
|
|
|
|
async function webmailSsoLogin(page, username, password, baseUrl) {
|
|
await gotoOnion(page, `${baseUrl}/`);
|
|
const ssoButton = page
|
|
.getByRole("button", { name: SSO_CONTROL })
|
|
.or(page.getByRole("link", { name: SSO_CONTROL }));
|
|
if (await ssoButton.first().isVisible().catch(() => false)) {
|
|
await ssoButton.first().click({ timeout: resolveTimeout(30_000) });
|
|
}
|
|
|
|
const mailUi = page.locator(MAIL_UI).first();
|
|
const usernameField = page
|
|
.getByRole("textbox", { name: /username|email/i })
|
|
.or(page.locator("input[name='username'], input#username"))
|
|
.first();
|
|
|
|
await Promise.any([
|
|
mailUi.waitFor({ state: "visible", timeout: resolveTimeout(60_000) }),
|
|
usernameField.waitFor({ state: "visible", timeout: resolveTimeout(60_000) }),
|
|
]);
|
|
if (await usernameField.isVisible().catch(() => false)) {
|
|
await performKeycloakLoginForm(page, username, password);
|
|
}
|
|
await mailUi.waitFor({ state: "visible", timeout: resolveTimeout(60_000) });
|
|
}
|
|
|
|
function messageRow(page, subject) {
|
|
return page
|
|
.locator("#messagelist tbody tr, table.messagelist tbody tr")
|
|
.filter({ hasText: subject })
|
|
.first();
|
|
}
|
|
|
|
async function waitForEmailInMailbox(page, baseUrl, subject, timeout = resolveTimeout(90_000)) {
|
|
const deadline = Date.now() + timeout;
|
|
while (Date.now() < deadline) {
|
|
for (const mailbox of MAILBOXES) {
|
|
await gotoOnion(page, `${baseUrl}/?_task=mail&_mbox=${encodeURIComponent(mailbox)}`, {
|
|
waitUntil: "domcontentloaded",
|
|
}).catch(() => {});
|
|
await page.waitForTimeout(resolveTimeout(2_000));
|
|
if (await messageRow(page, subject).isVisible().catch(() => false)) {
|
|
return messageRow(page, subject);
|
|
}
|
|
}
|
|
}
|
|
throw new Error(
|
|
`Timed out waiting for a message matching "${subject}" in ${MAILBOXES.join(" / ")}`
|
|
);
|
|
}
|
|
|
|
module.exports = { webmailSsoLogin, waitForEmailInMailbox };
|