665 lines
26 KiB
JavaScript
665 lines
26 KiB
JavaScript
const assert = require("node:assert/strict");
|
|
const path = require("node:path");
|
|
const {
|
|
Key,
|
|
absoluteUrl,
|
|
allTexts,
|
|
clickByTitle,
|
|
clickLabel,
|
|
clickSelector,
|
|
clickText,
|
|
elementText,
|
|
fillInput,
|
|
getAttribute,
|
|
getClassName,
|
|
getDomWrapErrors,
|
|
getValue,
|
|
hasSelector,
|
|
isChecked,
|
|
postJson,
|
|
registerAndLoginApi,
|
|
request,
|
|
runSmokeTests,
|
|
seedAuthenticatedBrowser,
|
|
selectorCount,
|
|
uniqueName,
|
|
waitFor,
|
|
waitForAbsent,
|
|
waitForSelector,
|
|
waitForText,
|
|
waitForUrl,
|
|
withDriver
|
|
} = require("./lib/selenium-smoke");
|
|
|
|
const domWrapAddonPath = path.join(__dirname, "dom-wrap-addon");
|
|
let bootstrapAdminSession = null;
|
|
|
|
async function ensureAdminSession() {
|
|
if (bootstrapAdminSession) {
|
|
return bootstrapAdminSession;
|
|
}
|
|
|
|
const username = uniqueName("bootstrap-admin");
|
|
bootstrapAdminSession = await registerAndLoginApi(username, "Bootstrap Admin");
|
|
return bootstrapAdminSession;
|
|
}
|
|
|
|
async function openAuthenticatedPlay(driver, sessionCookie) {
|
|
await seedAuthenticatedBrowser(driver, sessionCookie);
|
|
await driver.get(absoluteUrl("/play"));
|
|
await waitForText(driver, "Campaign Log");
|
|
}
|
|
|
|
const tests = [
|
|
{
|
|
name: "home page loads auth entry points",
|
|
run: async () => withDriver({}, async (driver) => {
|
|
await driver.get(absoluteUrl("/"));
|
|
await waitForUrl(driver, "/login");
|
|
await waitForText(driver, "RpgRoller");
|
|
assert.deepEqual(await allTexts(driver, "h2"), ["Register", "Login"]);
|
|
assert.equal(await hasSelector(driver, "#register-username"), true);
|
|
assert.equal(await hasSelector(driver, "#login-password"), true);
|
|
})
|
|
},
|
|
{
|
|
name: "root document redirects anonymous users to login",
|
|
run: async () => {
|
|
const response = await request("/", { redirect: "manual" });
|
|
assert.equal(response.status, 302);
|
|
assert.equal(response.headers.get("location"), "/login");
|
|
}
|
|
},
|
|
{
|
|
name: "login document renders static auth markup without bootstrapping blazor",
|
|
run: async () => {
|
|
const response = await request("/login");
|
|
assert.equal(response.status, 200);
|
|
|
|
const html = await response.text();
|
|
assert.ok(!html.includes("Connecting..."));
|
|
assert.ok(html.includes("Register or log in to join a campaign session."));
|
|
assert.ok(!html.includes("_framework/blazor.web.js"));
|
|
assert.ok(!html.includes("<!--Blazor:"));
|
|
assert.ok(html.includes("data-auth-page"));
|
|
}
|
|
},
|
|
{
|
|
name: "authenticated root document redirects to play",
|
|
run: async () => {
|
|
const { sessionCookie } = await ensureAdminSession();
|
|
const response = await request("/", {
|
|
cookie: sessionCookie,
|
|
redirect: "manual"
|
|
});
|
|
|
|
assert.equal(response.status, 302);
|
|
assert.equal(response.headers.get("location"), "/play");
|
|
}
|
|
},
|
|
{
|
|
name: "authenticated route navigation and refresh use real URLs",
|
|
run: async () => withDriver({}, async (driver) => {
|
|
const username = uniqueName("routes");
|
|
const { sessionCookie } = await registerAndLoginApi(username, "Route Navigation");
|
|
|
|
const campaign = await postJson("/api/campaigns", {
|
|
name: "Route Navigation",
|
|
rulesetId: "d6"
|
|
}, { cookie: sessionCookie });
|
|
|
|
await postJson("/api/characters", {
|
|
name: "Navigator",
|
|
campaignId: campaign.id
|
|
}, { cookie: sessionCookie });
|
|
|
|
await seedAuthenticatedBrowser(driver, sessionCookie);
|
|
await driver.get(absoluteUrl("/campaigns"));
|
|
await waitForUrl(driver, "/campaigns");
|
|
await waitForSelector(driver, "#campaign-select");
|
|
assert.equal(await hasSelector(driver, "#skill-filter-input"), false);
|
|
|
|
await driver.navigate().refresh();
|
|
await waitForUrl(driver, "/campaigns");
|
|
await waitForSelector(driver, "#campaign-select");
|
|
|
|
await clickSelector(driver, ".menu-toggle");
|
|
await clickText(driver, ".menu-item", "Play");
|
|
await waitForUrl(driver, "/play");
|
|
await waitForSelector(driver, "#skill-filter-input");
|
|
|
|
await driver.navigate().refresh();
|
|
await waitForUrl(driver, "/play");
|
|
await waitForSelector(driver, "#skill-filter-input");
|
|
|
|
await clickSelector(driver, ".menu-toggle");
|
|
await clickText(driver, ".menu-item", "Campaign Management");
|
|
await waitForUrl(driver, "/campaigns");
|
|
await waitForSelector(driver, "#campaign-select");
|
|
})
|
|
},
|
|
{
|
|
name: "non-admin users are redirected away from admin route",
|
|
run: async () => withDriver({}, async (driver) => {
|
|
const username = uniqueName("admin-redirect");
|
|
const { sessionCookie } = await registerAndLoginApi(username, "Admin Redirect");
|
|
|
|
await seedAuthenticatedBrowser(driver, sessionCookie);
|
|
await driver.get(absoluteUrl("/admin"));
|
|
|
|
await waitForUrl(driver, "/play");
|
|
await waitForText(driver, "Campaign Log");
|
|
assert.equal(await hasSelector(driver, ".management-list"), false);
|
|
})
|
|
},
|
|
{
|
|
name: "admin route mounts directly without play UI",
|
|
run: async () => withDriver({}, async (driver) => {
|
|
const { sessionCookie } = await ensureAdminSession();
|
|
|
|
await seedAuthenticatedBrowser(driver, sessionCookie);
|
|
await driver.get(absoluteUrl("/admin"));
|
|
|
|
await waitForUrl(driver, "/admin");
|
|
await waitForText(driver, "User Management");
|
|
assert.equal(await hasSelector(driver, ".management-list"), true);
|
|
assert.equal(await hasSelector(driver, "#skill-filter-input"), false);
|
|
assert.equal(await hasSelector(driver, ".log-panel"), false);
|
|
})
|
|
},
|
|
{
|
|
name: "successful login transitions to play workspace",
|
|
run: async () => withDriver({}, async (driver) => {
|
|
const username = uniqueName("login");
|
|
await postJson("/api/auth/register", {
|
|
username,
|
|
password: "Password123",
|
|
displayName: "Login Flow"
|
|
});
|
|
|
|
await driver.get(absoluteUrl("/login"));
|
|
await fillInput(driver, "#login-username", username);
|
|
await fillInput(driver, "#login-password", "Password123");
|
|
await clickText(driver, "button", "Login");
|
|
|
|
await waitForUrl(driver, "/play");
|
|
await waitForText(driver, "Campaign Log");
|
|
assert.equal(await selectorCount(driver, "#login-username"), 0);
|
|
})
|
|
},
|
|
{
|
|
name: "workspace stays usable when input controls are DOM-wrapped during mount",
|
|
run: async () => withDriver({ addonPath: domWrapAddonPath }, async (driver) => {
|
|
const username = uniqueName("wrapped");
|
|
const { sessionCookie } = await registerAndLoginApi(username, "Wrapped Inputs");
|
|
|
|
const campaign = await postJson("/api/campaigns", {
|
|
name: "Wrapped Inputs",
|
|
rulesetId: "d6"
|
|
}, { cookie: sessionCookie });
|
|
|
|
const character = await postJson("/api/characters", {
|
|
name: "Wrapper Hero",
|
|
campaignId: campaign.id
|
|
}, { cookie: sessionCookie });
|
|
|
|
await postJson(`/api/characters/${character.id}/skills`, {
|
|
name: "Stealth",
|
|
diceRollDefinition: "2D+1",
|
|
wildDice: 1,
|
|
allowFumble: true
|
|
}, { cookie: sessionCookie });
|
|
|
|
await openAuthenticatedPlay(driver, sessionCookie);
|
|
await waitForSelector(driver, "#skill-filter-input");
|
|
await waitForSelector(driver, "#custom-roll-expression");
|
|
await waitFor(
|
|
driver,
|
|
() => driver.executeScript(() => [...document.querySelectorAll("button")].some((button) => button.textContent.includes("Roll Stealth"))),
|
|
"Expected Roll Stealth button."
|
|
);
|
|
|
|
assert.deepEqual(await getDomWrapErrors(driver), []);
|
|
})
|
|
},
|
|
{
|
|
name: "Rolemaster open-ended roll detail renders specialized dice chips",
|
|
run: async () => withDriver({}, async (driver) => {
|
|
const username = uniqueName("rm");
|
|
const { sessionCookie } = await registerAndLoginApi(username, "Rolemaster Smoke");
|
|
|
|
const campaign = await postJson("/api/campaigns", {
|
|
name: "Rolemaster Smoke",
|
|
rulesetId: "rolemaster"
|
|
}, { cookie: sessionCookie });
|
|
|
|
const character = await postJson("/api/characters", {
|
|
name: "Open Ender",
|
|
campaignId: campaign.id
|
|
}, { cookie: sessionCookie });
|
|
|
|
const skill = await postJson(`/api/characters/${character.id}/skills`, {
|
|
name: "Open Sight",
|
|
diceRollDefinition: "d100!+85",
|
|
wildDice: 0,
|
|
allowFumble: false,
|
|
fumbleRange: 95
|
|
}, { cookie: sessionCookie });
|
|
|
|
let qualifyingRoll = null;
|
|
for (let attempt = 0; attempt < 12; attempt += 1) {
|
|
const roll = await postJson(`/api/skills/${skill.id}/roll`, { visibility: "public" }, { cookie: sessionCookie });
|
|
if (roll.dice.some((die) => die.kind === "rolemaster-open-ended-high" || die.kind === "rolemaster-open-ended-low-subtract")) {
|
|
qualifyingRoll = roll;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert.notEqual(qualifyingRoll, null, "Expected an open-ended Rolemaster roll within 12 attempts.");
|
|
|
|
await openAuthenticatedPlay(driver, sessionCookie);
|
|
await waitForSelector(driver, ".log-panel .log-entry");
|
|
|
|
await clickSelector(driver, ".log-panel .log-entry-toggle");
|
|
await waitForSelector(driver, ".die-chip.rolemaster-open-ended-high, .die-chip.rolemaster-open-ended-low-subtract");
|
|
assert.equal(await hasSelector(driver, ".log-detail .roll-dice-strip"), true);
|
|
})
|
|
},
|
|
{
|
|
name: "Rolemaster automatic retry badge shows before detail expands",
|
|
run: async () => withDriver({}, async (driver) => {
|
|
const username = uniqueName("rm-retry");
|
|
const { sessionCookie } = await registerAndLoginApi(username, "Rolemaster Retry Smoke");
|
|
|
|
const campaign = await postJson("/api/campaigns", {
|
|
name: "Rolemaster Retry Smoke",
|
|
rulesetId: "rolemaster"
|
|
}, { cookie: sessionCookie });
|
|
|
|
const character = await postJson("/api/characters", {
|
|
name: "Retry Hero",
|
|
campaignId: campaign.id
|
|
}, { cookie: sessionCookie });
|
|
|
|
const skill = await postJson(`/api/characters/${character.id}/skills`, {
|
|
name: "Retry Sight",
|
|
diceRollDefinition: "d100!+10",
|
|
wildDice: 0,
|
|
allowFumble: false,
|
|
fumbleRange: 5,
|
|
rolemasterAutoRetry: true
|
|
}, { cookie: sessionCookie });
|
|
|
|
let retriedRoll = null;
|
|
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
const roll = await postJson(`/api/skills/${skill.id}/roll`, { visibility: "public" }, { cookie: sessionCookie });
|
|
if (roll.breakdown.includes("retry(+")) {
|
|
retriedRoll = roll;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert.notEqual(retriedRoll, null, "Expected a retry-enabled Rolemaster roll within 10 attempts.");
|
|
|
|
await openAuthenticatedPlay(driver, sessionCookie);
|
|
await waitFor(
|
|
driver,
|
|
() => driver.executeScript(() => [...document.querySelectorAll(".log-panel .log-entry")].some((entry) => entry.textContent.includes("retry +"))),
|
|
"Expected retry roll entry."
|
|
);
|
|
|
|
const collapsedState = await driver.executeScript(() => {
|
|
const entries = [...document.querySelectorAll(".log-panel .log-entry")].filter((entry) => {
|
|
const text = entry.textContent || "";
|
|
return text.includes("Retry Sight") && text.includes("retry +");
|
|
});
|
|
const retryEntry = entries.at(-1);
|
|
if (!retryEntry) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
badgeTexts: [...retryEntry.querySelectorAll(".log-event-badge")].map((element) => element.textContent || ""),
|
|
summaryText: retryEntry.querySelector(".log-summary-text")?.textContent || "",
|
|
detailCount: retryEntry.querySelectorAll(".log-detail").length
|
|
};
|
|
});
|
|
|
|
assert.ok(collapsedState);
|
|
assert.ok(collapsedState.badgeTexts.some((badgeText) => /Retry \+(5|10)/.test(badgeText)));
|
|
assert.match(collapsedState.summaryText, /retry \+(5|10)/i);
|
|
assert.equal(collapsedState.detailCount, 0);
|
|
|
|
await clickText(driver, ".log-panel .log-entry-toggle", "Details", { contains: true, last: true }).catch(async () => {
|
|
const toggled = await driver.executeScript(() => {
|
|
const entries = [...document.querySelectorAll(".log-panel .log-entry")].filter((entry) => {
|
|
const text = entry.textContent || "";
|
|
return text.includes("Retry Sight") && text.includes("retry +");
|
|
});
|
|
const retryEntry = entries.at(-1);
|
|
const toggle = retryEntry?.querySelector(".log-entry-toggle");
|
|
if (!toggle) {
|
|
return false;
|
|
}
|
|
|
|
toggle.click();
|
|
return true;
|
|
});
|
|
|
|
assert.ok(toggled, "Could not expand retry entry.");
|
|
});
|
|
|
|
await waitFor(
|
|
driver,
|
|
() => driver.executeScript(() => {
|
|
const entries = [...document.querySelectorAll(".log-panel .log-entry")].filter((entry) => {
|
|
const text = entry.textContent || "";
|
|
return text.includes("Retry Sight") && text.includes("retry +");
|
|
});
|
|
return (entries.at(-1)?.querySelectorAll(".log-detail .die-chip").length || 0) === 2;
|
|
}),
|
|
"Expected two retry detail dice chips."
|
|
);
|
|
|
|
const detailState = await driver.executeScript(() => {
|
|
const entries = [...document.querySelectorAll(".log-panel .log-entry")].filter((entry) => {
|
|
const text = entry.textContent || "";
|
|
return text.includes("Retry Sight") && text.includes("retry +");
|
|
});
|
|
const retryEntry = entries.at(-1);
|
|
const chips = [...(retryEntry?.querySelectorAll(".log-detail .die-chip") || [])];
|
|
return chips.map((chip) => chip.getAttribute("title") || "");
|
|
});
|
|
|
|
assert.equal(detailState.length, 2);
|
|
assert.match(detailState[0], /attempt 1/i);
|
|
assert.match(detailState[1], /retry attempt 2/i);
|
|
})
|
|
},
|
|
{
|
|
name: "Rolemaster skill roll modal autofocuses, validates, and closes on escape or backdrop",
|
|
run: async () => withDriver({}, async (driver) => {
|
|
const username = uniqueName("rm-modal");
|
|
const { sessionCookie } = await registerAndLoginApi(username, "Rolemaster Modal Smoke");
|
|
|
|
const campaign = await postJson("/api/campaigns", {
|
|
name: "Rolemaster Modal Smoke",
|
|
rulesetId: "rolemaster"
|
|
}, { cookie: sessionCookie });
|
|
|
|
const character = await postJson("/api/characters", {
|
|
name: "Observer",
|
|
campaignId: campaign.id
|
|
}, { cookie: sessionCookie });
|
|
|
|
await postJson(`/api/characters/${character.id}/skills`, {
|
|
name: "Observation",
|
|
diceRollDefinition: "d100!+50",
|
|
wildDice: 0,
|
|
allowFumble: false,
|
|
fumbleRange: 5,
|
|
rolemasterAutoRetry: true
|
|
}, { cookie: sessionCookie });
|
|
|
|
await openAuthenticatedPlay(driver, sessionCookie);
|
|
await waitFor(
|
|
driver,
|
|
() => driver.executeScript(() => [...document.querySelectorAll("button")].some((button) => button.textContent.includes("Roll Observation"))),
|
|
"Expected Roll Observation button."
|
|
);
|
|
|
|
await clickText(driver, "button", "Roll Observation", { contains: true });
|
|
await waitForSelector(driver, ".rolemaster-roll-modal");
|
|
await waitFor(
|
|
driver,
|
|
() => driver.executeScript(() => document.activeElement?.id === "rolemaster-situational-modifier"),
|
|
"Expected modifier input to be focused."
|
|
);
|
|
|
|
await (await waitForSelector(driver, "#rolemaster-situational-modifier")).sendKeys(Key.ESCAPE);
|
|
await waitForAbsent(driver, ".rolemaster-roll-modal");
|
|
|
|
await clickText(driver, "button", "Roll Observation", { contains: true });
|
|
await waitForSelector(driver, ".rolemaster-roll-modal");
|
|
await driver.executeScript(() => {
|
|
document.querySelector(".modal-overlay")?.click();
|
|
});
|
|
await waitForAbsent(driver, ".rolemaster-roll-modal");
|
|
|
|
await clickText(driver, "button", "Roll Observation", { contains: true });
|
|
await waitForSelector(driver, ".rolemaster-roll-modal");
|
|
await fillInput(driver, "#rolemaster-situational-modifier", "1001");
|
|
await clickText(driver, ".rolemaster-roll-modal button", "Roll");
|
|
await waitForText(driver, "Enter a whole number between -1000 and 1000.");
|
|
assert.equal(await hasSelector(driver, ".rolemaster-roll-modal"), true);
|
|
|
|
await fillInput(driver, "#rolemaster-situational-modifier", "");
|
|
await (await waitForSelector(driver, "#rolemaster-situational-modifier")).sendKeys(Key.ENTER);
|
|
await waitForAbsent(driver, ".rolemaster-roll-modal");
|
|
await waitFor(
|
|
driver,
|
|
() => driver.executeScript(() => document.querySelector(".log-panel .log-entry.expanded")?.textContent.includes("Observation") || false),
|
|
"Expected expanded Observation log entry."
|
|
);
|
|
})
|
|
},
|
|
{
|
|
name: "newly rolled log entry auto-expands",
|
|
run: async () => withDriver({}, async (driver) => {
|
|
const username = uniqueName("d6-log");
|
|
const { sessionCookie } = await registerAndLoginApi(username, "D6 Auto Expand");
|
|
|
|
const campaign = await postJson("/api/campaigns", {
|
|
name: "D6 Auto Expand",
|
|
rulesetId: "d6"
|
|
}, { cookie: sessionCookie });
|
|
|
|
const character = await postJson("/api/characters", {
|
|
name: "Auto Hero",
|
|
campaignId: campaign.id
|
|
}, { cookie: sessionCookie });
|
|
|
|
await postJson(`/api/characters/${character.id}/skills`, {
|
|
name: "Stealth",
|
|
diceRollDefinition: "2D+1",
|
|
wildDice: 1,
|
|
allowFumble: true
|
|
}, { cookie: sessionCookie });
|
|
|
|
await openAuthenticatedPlay(driver, sessionCookie);
|
|
await waitFor(
|
|
driver,
|
|
() => driver.executeScript(() => [...document.querySelectorAll("button")].some((button) => button.textContent.includes("Roll Stealth"))),
|
|
"Expected Roll Stealth button."
|
|
);
|
|
await clickText(driver, "button", "Roll Stealth", { contains: true });
|
|
await waitForSelector(driver, ".log-panel .log-entry.expanded");
|
|
assert.equal(await hasSelector(driver, ".log-panel .log-entry.expanded .roll-dice-strip"), true);
|
|
})
|
|
},
|
|
{
|
|
name: "custom roll composer keeps parse errors inline and records successful rolls",
|
|
run: async () => withDriver({}, async (driver) => {
|
|
const username = uniqueName("custom-roll");
|
|
const { sessionCookie } = await registerAndLoginApi(username, "Custom Roller");
|
|
|
|
const campaign = await postJson("/api/campaigns", {
|
|
name: "Custom Roll Campaign",
|
|
rulesetId: "dnd5e"
|
|
}, { cookie: sessionCookie });
|
|
|
|
await postJson("/api/characters", {
|
|
name: "Improviser",
|
|
campaignId: campaign.id
|
|
}, { cookie: sessionCookie });
|
|
|
|
await openAuthenticatedPlay(driver, sessionCookie);
|
|
await waitFor(
|
|
driver,
|
|
() => driver.executeScript(() => {
|
|
const input = document.querySelector("#custom-roll-expression");
|
|
const button = document.querySelector(".custom-roll-composer button");
|
|
return Boolean(input && button && !input.disabled && !button.disabled);
|
|
}),
|
|
"Expected custom roll composer to be interactive."
|
|
);
|
|
assert.match(await elementText(driver, ".custom-roll-composer-head .muted"), /uses public visibility/i);
|
|
|
|
await fillInput(driver, "#custom-roll-expression", "bad");
|
|
await clickText(driver, ".custom-roll-composer button", "Roll");
|
|
|
|
await waitFor(
|
|
driver,
|
|
() => driver.executeScript(() => {
|
|
const input = document.querySelector("#custom-roll-expression");
|
|
return Boolean(input && /error/.test(input.className));
|
|
}),
|
|
"Expected custom roll input to show an inline validation error."
|
|
);
|
|
assert.match(await getClassName(driver, "#custom-roll-expression"), /error/);
|
|
assert.match(await getAttribute(driver, "#custom-roll-expression", "title"), /Expected dnd5e format like 2d12\+2\./);
|
|
assert.equal(await selectorCount(driver, ".toast.error"), 0);
|
|
|
|
await fillInput(driver, "#custom-roll-expression", "1d20+5");
|
|
await clickText(driver, ".custom-roll-composer button", "Roll");
|
|
await waitFor(
|
|
driver,
|
|
() => driver.executeScript(() => {
|
|
const className = document.querySelector("#custom-roll-expression")?.className || "";
|
|
const firstLogEntry = document.querySelector(".log-panel .log-entry");
|
|
return !/error/.test(className) &&
|
|
Boolean(firstLogEntry?.textContent.includes("Custom roll")) &&
|
|
Boolean(firstLogEntry?.textContent.includes("Public"));
|
|
}),
|
|
"Expected successful custom roll entry."
|
|
);
|
|
})
|
|
},
|
|
{
|
|
name: "Rolemaster UI exposes conditional create and edit fields",
|
|
run: async () => withDriver({}, async (driver) => {
|
|
const username = uniqueName("rm-ui");
|
|
const { sessionCookie } = await registerAndLoginApi(username, "Rolemaster UI");
|
|
|
|
const campaign = await postJson("/api/campaigns", {
|
|
name: "Rolemaster UI Campaign",
|
|
rulesetId: "rolemaster"
|
|
}, { cookie: sessionCookie });
|
|
|
|
const character = await postJson("/api/characters", {
|
|
name: "UI Character",
|
|
campaignId: campaign.id
|
|
}, { cookie: sessionCookie });
|
|
|
|
await postJson(`/api/characters/${character.id}/skill-groups`, {
|
|
name: "Awareness",
|
|
diceRollDefinition: "d100!+15",
|
|
wildDice: 0,
|
|
allowFumble: false,
|
|
fumbleRange: 5
|
|
}, { cookie: sessionCookie });
|
|
|
|
await postJson(`/api/characters/${character.id}/skills`, {
|
|
name: "Perception",
|
|
diceRollDefinition: "d100!+25",
|
|
wildDice: 0,
|
|
allowFumble: false,
|
|
fumbleRange: 5,
|
|
rolemasterAutoRetry: true
|
|
}, { cookie: sessionCookie });
|
|
|
|
await openAuthenticatedPlay(driver, sessionCookie);
|
|
await waitForSelector(driver, "#workspace-screen-menu-button");
|
|
|
|
await clickSelector(driver, "#workspace-screen-menu-button");
|
|
await clickText(driver, ".screen-menu .menu-item", "Campaign Management");
|
|
await waitFor(
|
|
driver,
|
|
() => driver.executeScript(() => [...document.querySelectorAll("button")].some((button) => button.textContent.includes("Add campaign"))),
|
|
"Expected Campaign Management controls."
|
|
);
|
|
await clickText(driver, "button", "Add campaign", { contains: true });
|
|
await waitForSelector(driver, "#campaign-ruleset");
|
|
assert.equal(await elementText(driver, "#campaign-ruleset option[value='rolemaster']"), "Rolemaster");
|
|
await clickText(driver, "button", "Cancel");
|
|
|
|
await clickSelector(driver, "#workspace-screen-menu-button");
|
|
await clickText(driver, ".screen-menu .menu-item", "Play");
|
|
await waitFor(
|
|
driver,
|
|
() => driver.executeScript(() => [...document.querySelectorAll("button")].some((button) => button.textContent.includes("Add group"))),
|
|
"Expected Play controls after returning from Campaign Management."
|
|
);
|
|
|
|
await clickText(driver, "button", "Add group", { contains: true });
|
|
await waitForSelector(driver, "#skill-group-expression");
|
|
assert.equal(await selectorCount(driver, "#skill-group-wild-dice"), 0);
|
|
assert.equal(await getValue(driver, "#skill-group-expression"), "d100");
|
|
await fillInput(driver, "#skill-group-expression", "d100!+15");
|
|
await waitForSelector(driver, "#skill-group-fumble-range");
|
|
await fillInput(driver, "#skill-group-fumble-range", "");
|
|
await clickText(driver, "button", "Create Group");
|
|
await waitForText(driver, "Open-ended Rolemaster groups require a fumble range.");
|
|
await clickText(driver, "button", "Cancel");
|
|
|
|
await clickText(driver, "button", "Add skill", { contains: true });
|
|
await waitForSelector(driver, "#skill-create-expression");
|
|
assert.equal(await getValue(driver, "#skill-create-expression"), "d100!+15");
|
|
await fillInput(driver, "#skill-create-expression", "15d10");
|
|
await waitFor(
|
|
driver,
|
|
() => selectorCount(driver, "#skill-create-fumble-range").then((count) => count === 0),
|
|
"Expected no create fumble range for non-open-ended expression."
|
|
);
|
|
await waitFor(
|
|
driver,
|
|
() => selectorCount(driver, "#skill-auto-retry").then((count) => count === 0),
|
|
"Expected no auto retry checkbox for non-open-ended expression."
|
|
);
|
|
|
|
await fillInput(driver, "#skill-create-expression", "d100!+25");
|
|
await waitForSelector(driver, "#skill-create-fumble-range");
|
|
await waitForSelector(driver, "#skill-auto-retry");
|
|
await clickLabel(driver, "Automatic retry");
|
|
await fillInput(driver, "#skill-create-expression", "d10");
|
|
await waitFor(
|
|
driver,
|
|
() => selectorCount(driver, "#skill-auto-retry").then((count) => count === 0),
|
|
"Expected create auto retry checkbox to disappear."
|
|
);
|
|
|
|
await fillInput(driver, "#skill-create-expression", "d100!+25");
|
|
await waitForSelector(driver, "#skill-auto-retry");
|
|
assert.equal(await isChecked(driver, "#skill-auto-retry"), false);
|
|
await clickText(driver, "button", "Cancel");
|
|
|
|
await clickByTitle(driver, "Edit skill");
|
|
await waitForSelector(driver, "#skill-edit-expression");
|
|
assert.equal(await getValue(driver, "#skill-edit-expression"), "d100!+25");
|
|
assert.equal(await getValue(driver, "#skill-edit-fumble-range"), "5");
|
|
assert.equal(await isChecked(driver, "#skill-auto-retry"), true);
|
|
await fillInput(driver, "#skill-edit-expression", "d10");
|
|
await waitFor(
|
|
driver,
|
|
() => selectorCount(driver, "#skill-edit-fumble-range").then((count) => count === 0),
|
|
"Expected edit fumble range to disappear."
|
|
);
|
|
await waitFor(
|
|
driver,
|
|
() => selectorCount(driver, "#skill-auto-retry").then((count) => count === 0),
|
|
"Expected edit auto retry checkbox to disappear."
|
|
);
|
|
|
|
await fillInput(driver, "#skill-edit-expression", "d100!+25");
|
|
await waitForSelector(driver, "#skill-auto-retry");
|
|
assert.equal(await isChecked(driver, "#skill-auto-retry"), false);
|
|
await clickText(driver, "button", "Cancel");
|
|
})
|
|
}
|
|
];
|
|
|
|
runSmokeTests(tests).catch((error) => {
|
|
console.error(error.stack || error);
|
|
process.exitCode = 1;
|
|
});
|