Align frontend smoke tests with current route permissions
Update self-serve mocked customer permissions for the guarded wash routes and replace removed replication-management E2E coverage with dashboard redirect assertions.
This commit is contained in:
@@ -4,6 +4,12 @@ import { mockApi, seedAuthenticatedState } from "./support/network.js";
|
||||
const removedMachineUnavailableGuidanceText =
|
||||
"Maskinvask er ikke tilgængelig for den valgte bane. Vælg manuel vask eller en anden ledig bane.";
|
||||
|
||||
const CUSTOMER_SELF_SERVE_PERMISSIONS = [
|
||||
"user",
|
||||
"list_own_department_selfserve_vehicle_conditions",
|
||||
"add_own_department_selfserve_vehicle_conditions",
|
||||
];
|
||||
|
||||
async function suppressVueDevtoolsOverlay(page) {
|
||||
await page.addInitScript(() => {
|
||||
const appendStyle = () => {
|
||||
@@ -27,7 +33,10 @@ async function primeSession(page, { token, permissions, sessionData = {} }) {
|
||||
await seedAuthenticatedState(page, token);
|
||||
}
|
||||
|
||||
async function mockSelectedSubuserGrant(page, { customerNumber = 12345679, permissions = ["SELFSERVE_LIST"] } = {}) {
|
||||
async function mockSelectedSubuserGrant(
|
||||
page,
|
||||
{ customerNumber = 12345679, permissions = ["SELFSERVE_LIST", "SELFSERVE_ADD"] } = {}
|
||||
) {
|
||||
await page.route("**/subusers/me", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
@@ -243,7 +252,7 @@ test.describe("Self-serve wash", () => {
|
||||
}) => {
|
||||
const api = await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: {
|
||||
customer_number: 12345679,
|
||||
},
|
||||
@@ -252,7 +261,7 @@ test.describe("Self-serve wash", () => {
|
||||
api.selfServe.commandResponseDelayMs = [500, 0];
|
||||
await primeSession(page, {
|
||||
token: "self-serve-user-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash");
|
||||
@@ -350,7 +359,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: {
|
||||
customer_number: 12345679,
|
||||
},
|
||||
@@ -358,7 +367,7 @@ test.describe("Self-serve wash", () => {
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-denied-geolocation-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start", { waitUntil: "domcontentloaded" });
|
||||
@@ -380,7 +389,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: {
|
||||
customer_number: 12345679,
|
||||
},
|
||||
@@ -426,7 +435,7 @@ test.describe("Self-serve wash", () => {
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-active-server-wash-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -533,7 +542,7 @@ test.describe("Self-serve wash", () => {
|
||||
const requests = captureSelfServeGatewayRequests(page);
|
||||
const api = await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: true,
|
||||
});
|
||||
api.selfServe.answerResponseDelayMs = 1000;
|
||||
@@ -554,7 +563,7 @@ test.describe("Self-serve wash", () => {
|
||||
};
|
||||
await primeSession(page, {
|
||||
token: "self-serve-empty-questions-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -580,7 +589,7 @@ test.describe("Self-serve wash", () => {
|
||||
test("start route reviews prefilled answered questions before lane confirmation", async ({ page }) => {
|
||||
const api = await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: true,
|
||||
});
|
||||
const answeredQuestion = {
|
||||
@@ -604,7 +613,7 @@ test.describe("Self-serve wash", () => {
|
||||
};
|
||||
await primeSession(page, {
|
||||
token: "self-serve-prefilled-questions-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start", { waitUntil: "domcontentloaded" });
|
||||
@@ -626,7 +635,7 @@ test.describe("Self-serve wash", () => {
|
||||
test("vehicle next button is disabled while self-serve data loads", async ({ page }) => {
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: {
|
||||
previewResponseDelayMs: 1000,
|
||||
summaryResponseDelayMs: 1000,
|
||||
@@ -634,7 +643,7 @@ test.describe("Self-serve wash", () => {
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-next-loading-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -670,12 +679,12 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: true,
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-direct-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -725,7 +734,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: {
|
||||
departments: [
|
||||
{
|
||||
@@ -789,7 +798,7 @@ test.describe("Self-serve wash", () => {
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-disabled-lane-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -811,7 +820,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: {
|
||||
departments: [
|
||||
{
|
||||
@@ -841,7 +850,7 @@ test.describe("Self-serve wash", () => {
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-unavailable-lane-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -862,7 +871,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: {
|
||||
departments: [
|
||||
{
|
||||
@@ -927,7 +936,7 @@ test.describe("Self-serve wash", () => {
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-stale-unavailable-lane-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -957,7 +966,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: {
|
||||
customer_number: 12345679,
|
||||
runtime_config: {
|
||||
@@ -970,7 +979,7 @@ test.describe("Self-serve wash", () => {
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-customer-number-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1007,7 +1016,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: {
|
||||
customer_number: null,
|
||||
},
|
||||
@@ -1016,7 +1025,7 @@ test.describe("Self-serve wash", () => {
|
||||
await mockSelectedSubuserGrant(page);
|
||||
await primeSession(page, {
|
||||
token: "self-serve-subuser-grant-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1048,7 +1057,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
const api = await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: {
|
||||
customer_number: 12345679,
|
||||
},
|
||||
@@ -1085,7 +1094,7 @@ test.describe("Self-serve wash", () => {
|
||||
};
|
||||
await primeSession(page, {
|
||||
token: "self-serve-start-retry-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1114,7 +1123,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: {
|
||||
commandResponses: [
|
||||
{
|
||||
@@ -1147,7 +1156,7 @@ test.describe("Self-serve wash", () => {
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-stop-failure-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1181,7 +1190,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: {
|
||||
commandResponseDelayMs: [500, 0],
|
||||
commandResponses: [
|
||||
@@ -1196,7 +1205,7 @@ test.describe("Self-serve wash", () => {
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-stop-already-ended-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1228,12 +1237,12 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: true,
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-edge-readonly-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1280,7 +1289,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
const api = await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: true,
|
||||
});
|
||||
api.selfServe.answerResponseDelayMs = 1000;
|
||||
@@ -1305,7 +1314,7 @@ test.describe("Self-serve wash", () => {
|
||||
};
|
||||
await primeSession(page, {
|
||||
token: "self-serve-background-answer-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1340,7 +1349,7 @@ test.describe("Self-serve wash", () => {
|
||||
test("question cards keep their position when answer refreshes return reordered questions", async ({ page }) => {
|
||||
const api = await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: true,
|
||||
});
|
||||
api.selfServe.answerResponseDelayMs = 1000;
|
||||
@@ -1361,7 +1370,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await primeSession(page, {
|
||||
token: "self-serve-stable-question-order-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1435,7 +1444,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: {
|
||||
laneAllowedServiceResponses: [
|
||||
{
|
||||
@@ -1455,7 +1464,7 @@ test.describe("Self-serve wash", () => {
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-edge-services-retry-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1488,7 +1497,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
const api = await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: {
|
||||
laneAllowedServices: [],
|
||||
},
|
||||
@@ -1506,7 +1515,7 @@ test.describe("Self-serve wash", () => {
|
||||
}
|
||||
await primeSession(page, {
|
||||
token: "self-serve-machine-globally-disabled-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1545,7 +1554,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
const api = await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: true,
|
||||
});
|
||||
api.selfServe.dynamicImageDelayMs = [750, 700, 700];
|
||||
@@ -1581,7 +1590,7 @@ test.describe("Self-serve wash", () => {
|
||||
api.selfServe.summaryByKey["7:AB12345"] = answeredSummary;
|
||||
await primeSession(page, {
|
||||
token: "self-serve-tasks-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start", { waitUntil: "domcontentloaded" });
|
||||
@@ -1657,7 +1666,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
const api = await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: true,
|
||||
});
|
||||
const answeredSummary = api.selfServe.answerResponseByKey["7:AB12345:11:true"];
|
||||
@@ -1704,7 +1713,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await primeSession(page, {
|
||||
token: "self-serve-machine-start-stale-summary-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1758,7 +1767,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
const api = await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: true,
|
||||
});
|
||||
const answeredSummary = api.selfServe.answerResponseByKey["7:AB12345:11:true"];
|
||||
@@ -1789,7 +1798,7 @@ test.describe("Self-serve wash", () => {
|
||||
api.selfServe.summaryByKey["7:AB12345"] = answeredSummary;
|
||||
await primeSession(page, {
|
||||
token: "self-serve-manual-task-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1821,7 +1830,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
const api = await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: true,
|
||||
});
|
||||
const buttonTask = {
|
||||
@@ -1871,7 +1880,7 @@ test.describe("Self-serve wash", () => {
|
||||
api.selfServe.summaryByKey["7:AB12345"] = blockedSummary;
|
||||
await primeSession(page, {
|
||||
token: "self-serve-machine-blocked-task-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1899,12 +1908,12 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: true,
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-property-gates-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -1934,12 +1943,12 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
selfServe: true,
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-mobile-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
@@ -2000,7 +2009,7 @@ test.describe("Self-serve wash", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: {
|
||||
customer_number: 12345679,
|
||||
},
|
||||
@@ -2025,7 +2034,7 @@ test.describe("Self-serve wash", () => {
|
||||
});
|
||||
await primeSession(page, {
|
||||
token: "self-serve-polling-completed-token",
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
});
|
||||
|
||||
await page.goto("/user/wash/start");
|
||||
|
||||
@@ -25,44 +25,6 @@ async function bootSuperuser(page, { permissions = ["superuser", "user"], edgeGa
|
||||
await primeSuperuserSession(page);
|
||||
}
|
||||
|
||||
async function waitForLayoutFrame(page) {
|
||||
await page.evaluate(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
requestAnimationFrame(() => requestAnimationFrame(resolve));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async function getActionMenuVerticalPlacement(trigger, menu) {
|
||||
const [triggerBox, menuBox] = await Promise.all([trigger.boundingBox(), menu.boundingBox()]);
|
||||
|
||||
expect(triggerBox).not.toBeNull();
|
||||
expect(menuBox).not.toBeNull();
|
||||
|
||||
const belowGap = menuBox.y - (triggerBox.y + triggerBox.height);
|
||||
const aboveGap = triggerBox.y - (menuBox.y + menuBox.height);
|
||||
const isBelowButton = belowGap >= -2 && belowGap <= 16;
|
||||
const isAboveButton = aboveGap >= -2 && aboveGap <= 16;
|
||||
|
||||
expect(isBelowButton || isAboveButton).toBe(true);
|
||||
expect(menuBox.y >= triggerBox.y + triggerBox.height - 2 || menuBox.y + menuBox.height <= triggerBox.y + 2).toBe(
|
||||
true
|
||||
);
|
||||
|
||||
return isBelowButton ? "below" : "above";
|
||||
}
|
||||
|
||||
async function getSettledActionMenuVerticalPlacement(page, trigger, menu) {
|
||||
await waitForLayoutFrame(page);
|
||||
const firstPlacement = await getActionMenuVerticalPlacement(trigger, menu);
|
||||
await waitForLayoutFrame(page);
|
||||
const secondPlacement = await getActionMenuVerticalPlacement(trigger, menu);
|
||||
expect(secondPlacement).toBe(firstPlacement);
|
||||
|
||||
return secondPlacement;
|
||||
}
|
||||
|
||||
async function installSystemStatusMock(page, snapshot) {
|
||||
await page.route(/\/superuser\/system\/status(?:\?.*)?$/, async (route) => {
|
||||
await route.fulfill(
|
||||
@@ -76,531 +38,6 @@ async function installSystemStatusMock(page, snapshot) {
|
||||
});
|
||||
}
|
||||
|
||||
async function installReplicationMock(page) {
|
||||
const requests = {
|
||||
composeKinds: [],
|
||||
credentialTestKinds: [],
|
||||
addKinds: [],
|
||||
provisionKinds: [],
|
||||
hostTestKinds: [],
|
||||
};
|
||||
|
||||
await page.route(/\/superuser\/replication\/(databases|redis|minio)\/\d+\/test$/, async (route) => {
|
||||
const match = route
|
||||
.request()
|
||||
.url()
|
||||
.match(/\/superuser\/replication\/(databases|redis|minio)\//);
|
||||
const pathKind = match?.[1] || "databases";
|
||||
requests.hostTestKinds.push(pathKind === "databases" ? "database" : pathKind);
|
||||
await route.fulfill(
|
||||
json({
|
||||
success: true,
|
||||
data: {
|
||||
ok: true,
|
||||
status: { status: "ok", replication_percent: 100, blockers: [] },
|
||||
},
|
||||
meta: {},
|
||||
includes: {},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await page.route(/\/superuser\/replication\/databases\/77\/provision$/, async (route) => {
|
||||
requests.provisionKinds.push("database");
|
||||
await route.fulfill(
|
||||
json({
|
||||
success: true,
|
||||
data: {
|
||||
ok: true,
|
||||
message: "MariaDB replication was configured with GTID slave_pos.",
|
||||
replication_percent: 100,
|
||||
blockers: [],
|
||||
host: { id: 77, kind: "database", label: "mariadb-replica-1", role: "replica" },
|
||||
},
|
||||
meta: {},
|
||||
includes: {},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await page.route(/\/superuser\/replication\/redis\/88\/provision$/, async (route) => {
|
||||
requests.provisionKinds.push("redis");
|
||||
await route.fulfill(
|
||||
json({
|
||||
success: true,
|
||||
data: {
|
||||
ok: true,
|
||||
message: "Redis replication was configured.",
|
||||
replication_percent: 100,
|
||||
blockers: [],
|
||||
host: { id: 88, kind: "redis", label: "redis-replica-1", role: "replica" },
|
||||
},
|
||||
meta: {},
|
||||
includes: {},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await page.route(/\/superuser\/replication\/minio\/99\/provision$/, async (route) => {
|
||||
requests.provisionKinds.push("minio");
|
||||
await route.fulfill(
|
||||
json({
|
||||
success: true,
|
||||
data: {
|
||||
ok: true,
|
||||
message: "MinIO bucket replication was configured.",
|
||||
replication_percent: 100,
|
||||
blockers: [],
|
||||
host: { id: 99, kind: "minio", label: "minio-replica-1", role: "replica" },
|
||||
},
|
||||
meta: {},
|
||||
includes: {},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await page.route(/\/superuser\/replication\/databases$/, async (route) => {
|
||||
requests.addKinds.push("database");
|
||||
await route.fulfill(
|
||||
json(
|
||||
{
|
||||
success: true,
|
||||
data: {
|
||||
id: 77,
|
||||
kind: "database",
|
||||
label: "mariadb-replica-1",
|
||||
host: "db.example.test",
|
||||
port: 3307,
|
||||
role: "replica",
|
||||
status: "unknown",
|
||||
replication_percent: 0,
|
||||
},
|
||||
meta: {},
|
||||
includes: {},
|
||||
},
|
||||
201
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
await page.route(/\/superuser\/replication\/redis$/, async (route) => {
|
||||
requests.addKinds.push("redis");
|
||||
await route.fulfill(
|
||||
json(
|
||||
{
|
||||
success: true,
|
||||
data: {
|
||||
id: 88,
|
||||
kind: "redis",
|
||||
label: "redis-replica-1",
|
||||
host: "redis-replica.example.test",
|
||||
port: 6380,
|
||||
role: "replica",
|
||||
status: "unknown",
|
||||
replication_percent: 0,
|
||||
},
|
||||
meta: {},
|
||||
includes: {},
|
||||
},
|
||||
201
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
await page.route(/\/superuser\/replication\/minio$/, async (route) => {
|
||||
requests.addKinds.push("minio");
|
||||
await route.fulfill(
|
||||
json(
|
||||
{
|
||||
success: true,
|
||||
data: {
|
||||
id: 99,
|
||||
kind: "minio",
|
||||
label: "minio-replica-1",
|
||||
host: "minio-replica.example.test",
|
||||
port: 9010,
|
||||
role: "replica",
|
||||
status: "unknown",
|
||||
replication_percent: 0,
|
||||
},
|
||||
meta: {},
|
||||
includes: {},
|
||||
},
|
||||
201
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
await page.route(/\/superuser\/replication\/compose-template$/, async (route) => {
|
||||
const payload = JSON.parse(route.request().postData() || "{}");
|
||||
requests.composeKinds.push(payload.kind || "database");
|
||||
if (payload.kind === "minio") {
|
||||
await route.fulfill(
|
||||
json({
|
||||
success: true,
|
||||
data: {
|
||||
kind: "minio",
|
||||
engine: "minio",
|
||||
role: "replica",
|
||||
service_name: "minio-replica-1",
|
||||
host_port: 9010,
|
||||
console_port: 9011,
|
||||
compose:
|
||||
'services:\n minio-replica-1:\n image: "minio/minio:latest"\n command:\n - server\n - /data\n minio-replica-1-setup:\n image: "minio/mc:latest"\n',
|
||||
env: "MINIO_ROOT_USER=twminio0123456789abcdef01234567\nMINIO_ROOT_PASSWORD=generated-minio-secret\nMINIO_SERVER_URL=http://minio-replica.example.test:9010\nMINIO_BROWSER_REDIRECT_URL=http://minio-replica.example.test:9011\nMINIO_BUCKETS=attachments,uploads\nMINIO_PRIMARY_ENDPOINT=https://minio-primary.internal:9000\nMINIO_PRIMARY_ACCESS_KEY=primary-access\nMINIO_PRIMARY_SECRET_KEY=primary-secret\n",
|
||||
credentials: {
|
||||
label: "minio-replica-1",
|
||||
host: "",
|
||||
port: 9010,
|
||||
scheme: "http",
|
||||
endpoint: "",
|
||||
buckets: ["attachments", "uploads"],
|
||||
console_port: 9011,
|
||||
username: "twminio0123456789abcdef01234567",
|
||||
password: "generated-minio-secret",
|
||||
space_headroom_percent: 20,
|
||||
},
|
||||
steps: [],
|
||||
},
|
||||
meta: {},
|
||||
includes: {},
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (payload.kind === "redis") {
|
||||
await route.fulfill(
|
||||
json({
|
||||
success: true,
|
||||
data: {
|
||||
kind: "redis",
|
||||
engine: "redis",
|
||||
role: "replica",
|
||||
service_name: "redis-replica-1",
|
||||
host_port: 6380,
|
||||
compose:
|
||||
'services:\n redis-replica-1:\n image: "redis:7"\n command:\n - /bin/sh\n - -ec\n - |\n if [ ! -f /data/redis.conf ]; then\n echo "replicaof $$REDIS_PRIMARY_HOST $${REDIS_PRIMARY_PORT:-6379}" > /data/redis.conf\n fi\n exec redis-server /data/redis.conf\n',
|
||||
env: "REDIS_PASSWORD=generated-redis-password\nREDIS_PRIMARY_HOST=redis-primary\nREDIS_PRIMARY_PORT=6379\nREDIS_PRIMARY_PASSWORD=\nREDIS_PRIMARY_USERNAME=\n",
|
||||
credentials: {
|
||||
label: "redis-replica-1",
|
||||
host: "",
|
||||
port: 6380,
|
||||
database: 0,
|
||||
username: "",
|
||||
password: "generated-redis-password",
|
||||
},
|
||||
steps: [],
|
||||
},
|
||||
meta: {},
|
||||
includes: {},
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await route.fulfill(
|
||||
json({
|
||||
success: true,
|
||||
data: {
|
||||
kind: "database",
|
||||
engine: "mariadb",
|
||||
role: "replica",
|
||||
service_name: "mariadb-replica-1",
|
||||
host_port: 3307,
|
||||
server_id: 2,
|
||||
compose:
|
||||
'services:\n mariadb-replica-1:\n image: "mariadb:11"\n command:\n - "--log-bin=/var/lib/mysql/mariadb-bin"\n mariadb-replica-1-seed:\n image: "mariadb:11"\n',
|
||||
env: "MARIADB_ROOT_PASSWORD=generated-root-password\nMARIADB_PASSWORD=generated-app-password\nMARIADB_PRIMARY_ADMIN_PASSWORD=\n",
|
||||
seed_command:
|
||||
"mariadb-dump --gtid --master-data=2 --databases nnks_db | mariadb --host=db.example.test --port=3307",
|
||||
credentials: {
|
||||
label: "mariadb-replica-1",
|
||||
host: "",
|
||||
port: 3307,
|
||||
database: "nnks_db",
|
||||
username: "nnks_db_user",
|
||||
password: "generated-app-password",
|
||||
admin_username: "root",
|
||||
admin_password: "generated-root-password",
|
||||
replication_username: "replication",
|
||||
replication_password: "generated-replication-password",
|
||||
ssl_mode: "DISABLED",
|
||||
allow_preseeded_replica: true,
|
||||
},
|
||||
steps: [],
|
||||
},
|
||||
meta: {},
|
||||
includes: {},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await page.route(/\/superuser\/replication\/test-credentials$/, async (route) => {
|
||||
const payload = JSON.parse(route.request().postData() || "{}");
|
||||
const kind = payload.kind || "database";
|
||||
requests.credentialTestKinds.push(kind);
|
||||
await route.fulfill(
|
||||
json({
|
||||
success: true,
|
||||
data: {
|
||||
ok: true,
|
||||
host: {
|
||||
id: 0,
|
||||
kind,
|
||||
label: kind === "redis" ? "redis-replica-1" : kind === "minio" ? "minio-replica-1" : "mariadb-replica-1",
|
||||
host:
|
||||
kind === "redis"
|
||||
? "redis-replica.example.test"
|
||||
: kind === "minio"
|
||||
? "minio-replica.example.test"
|
||||
: "db.example.test",
|
||||
port: kind === "redis" ? 6380 : kind === "minio" ? 9010 : 3307,
|
||||
},
|
||||
status: { status: "ok", replication_percent: 100, blockers: [] },
|
||||
},
|
||||
meta: {},
|
||||
includes: {},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await page.route(/\/superuser\/replication(?:\?.*)?$/, async (route) => {
|
||||
await route.fulfill(
|
||||
json({
|
||||
success: true,
|
||||
data: {
|
||||
generated_at: "2026-04-08T08:45:00.000Z",
|
||||
database: {
|
||||
primary: {
|
||||
id: 1,
|
||||
kind: "database",
|
||||
label: "mysql-primary",
|
||||
host: "mysql-primary",
|
||||
port: 3306,
|
||||
role: "primary",
|
||||
status: "ok",
|
||||
replication_percent: 100,
|
||||
},
|
||||
hosts: [
|
||||
{
|
||||
id: 1,
|
||||
kind: "database",
|
||||
label: "mysql-primary",
|
||||
host: "mysql-primary",
|
||||
port: 3306,
|
||||
role: "primary",
|
||||
status: "ok",
|
||||
replication_percent: 100,
|
||||
last_status: { blockers: [] },
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
kind: "database",
|
||||
label: "mysql-replica-1",
|
||||
host: "mysql-replica-1",
|
||||
port: 3306,
|
||||
role: "replica",
|
||||
status: "provisioning",
|
||||
replication_percent: 42.5,
|
||||
last_checked_at: "2026-04-08T08:45:00.000Z",
|
||||
last_status: {
|
||||
blockers: [
|
||||
"Replica seed includes data for schema-only tables: nnks_db.edge_gateway_log_entries. Re-run provisioning to rebuild the replica without operational log data.",
|
||||
],
|
||||
replication_percent: 42.5,
|
||||
status: "provisioning",
|
||||
},
|
||||
active_operation: {
|
||||
id: 99,
|
||||
operation: "provision",
|
||||
status: "running",
|
||||
progress_percent: 42.5,
|
||||
message: "Copying replica data for bookings.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
kind: "database",
|
||||
label: "mysql-replica-caught-up",
|
||||
host: "mysql-replica-caught-up",
|
||||
port: 3306,
|
||||
role: "replica",
|
||||
status: "ok",
|
||||
replication_percent: 100,
|
||||
last_checked_at: "2026-04-08T08:45:00.000Z",
|
||||
last_status: { blockers: [], replication_percent: 100, status: "ok" },
|
||||
},
|
||||
],
|
||||
replication: {
|
||||
status: "degraded",
|
||||
min_percent: 42.5,
|
||||
average_percent: 42.5,
|
||||
replicas: [
|
||||
{ id: 2, label: "mysql-replica-1", replication_percent: 42.5 },
|
||||
{ id: 7, label: "mysql-replica-caught-up", replication_percent: 100 },
|
||||
],
|
||||
blockers: [],
|
||||
},
|
||||
},
|
||||
redis: {
|
||||
primary: {
|
||||
id: 3,
|
||||
kind: "redis",
|
||||
label: "redis-primary",
|
||||
host: "redis-primary",
|
||||
port: 6379,
|
||||
role: "primary",
|
||||
status: "ok",
|
||||
replication_percent: 100,
|
||||
},
|
||||
hosts: [
|
||||
{
|
||||
id: 3,
|
||||
kind: "redis",
|
||||
label: "redis-primary",
|
||||
host: "redis-primary",
|
||||
port: 6379,
|
||||
role: "primary",
|
||||
status: "ok",
|
||||
replication_percent: 100,
|
||||
last_status: { blockers: [] },
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
kind: "redis",
|
||||
label: "redis-replica-1",
|
||||
host: "redis-replica-1",
|
||||
port: 6379,
|
||||
role: "replica",
|
||||
status: "degraded",
|
||||
replication_percent: 81.2,
|
||||
last_checked_at: "2026-04-08T08:45:00.000Z",
|
||||
last_status: { blockers: ["Redis replica link to primary is not up."], replication_percent: 81.2 },
|
||||
},
|
||||
],
|
||||
replication: {
|
||||
status: "degraded",
|
||||
min_percent: 81.2,
|
||||
average_percent: 81.2,
|
||||
replicas: [{ id: 4, label: "redis-replica-1", replication_percent: 81.2 }],
|
||||
blockers: ["Redis replica link to primary is not up."],
|
||||
},
|
||||
},
|
||||
minio: {
|
||||
primary: {
|
||||
id: 5,
|
||||
kind: "minio",
|
||||
label: "minio-primary",
|
||||
host: "minio-primary",
|
||||
port: 9000,
|
||||
endpoint: "http://minio-primary:9000",
|
||||
role: "primary",
|
||||
status: "ok",
|
||||
replication_percent: 100,
|
||||
},
|
||||
hosts: [
|
||||
{
|
||||
id: 5,
|
||||
kind: "minio",
|
||||
label: "minio-primary",
|
||||
host: "minio-primary",
|
||||
port: 9000,
|
||||
endpoint: "http://minio-primary:9000",
|
||||
role: "primary",
|
||||
status: "ok",
|
||||
replication_percent: 100,
|
||||
last_status: { blockers: [] },
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
kind: "minio",
|
||||
label: "minio-replica-1",
|
||||
host: "minio-replica-1",
|
||||
port: 9010,
|
||||
endpoint: "http://minio-replica-1:9000",
|
||||
role: "replica",
|
||||
status: "ok",
|
||||
replication_percent: 67.4,
|
||||
last_checked_at: "2026-04-08T08:45:00.000Z",
|
||||
last_status: {
|
||||
status: "ok",
|
||||
blockers: ["MinIO replica has not caught up."],
|
||||
replication_percent: 67.4,
|
||||
},
|
||||
},
|
||||
],
|
||||
replication: {
|
||||
status: "degraded",
|
||||
min_percent: 67.4,
|
||||
average_percent: 67.4,
|
||||
replicas: [{ id: 6, label: "minio-replica-1", replication_percent: 67.4 }],
|
||||
blockers: ["MinIO replica has not caught up."],
|
||||
},
|
||||
},
|
||||
write_freeze: {},
|
||||
},
|
||||
meta: {},
|
||||
includes: {},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return requests;
|
||||
}
|
||||
|
||||
function createGateway(id, overrides = {}) {
|
||||
return {
|
||||
id,
|
||||
label: `Gateway ${id}`,
|
||||
hostname: `gateway-${id}.truckwash.test`,
|
||||
department_id: 11,
|
||||
status: "ONLINE",
|
||||
discovery_status: "READY",
|
||||
last_heartbeat_at: "2026-04-08T09:00:00.000Z",
|
||||
active_operation: null,
|
||||
diagnostics: [],
|
||||
error_state: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createGatewayFleetUsage(rows) {
|
||||
return {
|
||||
gateways: {
|
||||
total: rows.length,
|
||||
online: rows.filter((gateway) => gateway.status === "ONLINE").length,
|
||||
degraded: rows.filter((gateway) => gateway.status === "DEGRADED").length,
|
||||
offline: rows.filter((gateway) => gateway.status === "OFFLINE").length,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function installGatewayFleetMock(page, { rows, departments }) {
|
||||
await page.route(/\/edge-gateways(?:\?.*)?$/, async (route) => {
|
||||
await route.fulfill(
|
||||
json({
|
||||
success: true,
|
||||
data: rows,
|
||||
meta: {
|
||||
fleet_usage: createGatewayFleetUsage(rows),
|
||||
},
|
||||
includes: {},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await page.route(/\/departments(?:\?.*)?$/, async (route) => {
|
||||
await route.fulfill(
|
||||
json({
|
||||
success: true,
|
||||
data: departments,
|
||||
meta: {},
|
||||
includes: {},
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const degradedSnapshot = {
|
||||
overall_status: "degraded",
|
||||
generated_at: "2026-04-08T08:45:00.000Z",
|
||||
@@ -853,190 +290,33 @@ test.describe("Superuser system status smoke", () => {
|
||||
await expect(page.getByTestId("database-status-card")).toContainText("8.0.36");
|
||||
});
|
||||
|
||||
test("@smoke @pr superuser replication action menu keeps stable vertical placement", async ({ page }) => {
|
||||
test("@smoke @pr superuser replication route redirects to the system dashboard", async ({ page }) => {
|
||||
await bootSuperuser(page);
|
||||
await installReplicationMock(page);
|
||||
await installSystemStatusMock(page, degradedSnapshot);
|
||||
|
||||
await page.goto("/superuser/system/replication");
|
||||
|
||||
await expect(page.getByTestId("replication-management-page")).toBeVisible({ timeout: PAGE_READY_TIMEOUT });
|
||||
await expect(page.getByTestId("replication-host-database-2")).toContainText("mysql-replica-1");
|
||||
|
||||
const replicaActions = page.getByTestId("replication-actions-database-2");
|
||||
const replicaActionTrigger = replicaActions.locator(".action-settings-wheel-trigger");
|
||||
const replicaActionMenu = replicaActions.locator(".dropdown-content");
|
||||
|
||||
await expect(replicaActionTrigger).toBeVisible();
|
||||
await replicaActionTrigger.click();
|
||||
await expect(replicaActions.locator(".dropdown-item-action:visible")).toHaveCount(4);
|
||||
const initialPlacement = await getSettledActionMenuVerticalPlacement(page, replicaActionTrigger, replicaActionMenu);
|
||||
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(replicaActionTrigger).not.toHaveClass(/action-settings-wheel-trigger--active/);
|
||||
|
||||
await replicaActionTrigger.click();
|
||||
await expect(replicaActions.locator(".dropdown-item-action:visible")).toHaveCount(4);
|
||||
const reopenedPlacement = await getSettledActionMenuVerticalPlacement(
|
||||
page,
|
||||
replicaActionTrigger,
|
||||
replicaActionMenu
|
||||
);
|
||||
|
||||
expect(reopenedPlacement).toBe(initialPlacement);
|
||||
await expect(page).toHaveURL(/\/superuser$/);
|
||||
await expect(page.getByTestId("system-status-dashboard")).toBeVisible({ timeout: PAGE_READY_TIMEOUT });
|
||||
await expect(page.getByTestId("status-card-database-replication")).toContainText("100.0% replikering");
|
||||
await expect(page.getByTestId("status-card-minio-replication")).toContainText("67.4% replikering");
|
||||
await expect(page.locator('a[href="/superuser/system/replication"]')).toBeVisible();
|
||||
await expect(page.getByTestId("replication-management-page")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("@smoke superuser replication management page renders topology and percentages", async ({ page }) => {
|
||||
test("@smoke superuser replication dashboard link preserves infrastructure snapshot", async ({ page }) => {
|
||||
await bootSuperuser(page);
|
||||
const replicationRequests = await installReplicationMock(page);
|
||||
await installSystemStatusMock(page, degradedSnapshot);
|
||||
|
||||
await page.goto("/superuser/system/replication");
|
||||
await page.goto("/superuser");
|
||||
await expect(page.getByTestId("system-status-dashboard")).toBeVisible({ timeout: PAGE_READY_TIMEOUT });
|
||||
await page.locator('a[href="/superuser/system/replication"]').click();
|
||||
|
||||
await expect(page).toHaveURL(/\/superuser\/system\/replication$/);
|
||||
await expect(page.getByTestId("replication-management-page")).toBeVisible({ timeout: PAGE_READY_TIMEOUT });
|
||||
await expect(page.getByTestId("replication-summary-database")).toContainText("42.5%");
|
||||
await expect(page.getByTestId("replication-summary-redis")).toContainText("81.2%");
|
||||
await expect(page.getByTestId("replication-summary-minio")).toContainText("67.4%");
|
||||
await expect(page.getByTestId("replication-host-database-2")).toContainText("mysql-replica-1");
|
||||
await expect(page.getByTestId("replication-host-database-2")).toContainText("42.5%");
|
||||
await expect(page.getByTestId("replication-host-database-2")).toContainText("Copying replica data for bookings.");
|
||||
await expect(
|
||||
page.getByTestId("replication-host-database-2").getByTestId("replication-host-progress")
|
||||
).toHaveAttribute("value", /^42\.5$/);
|
||||
await expect(page.getByTestId("replication-host-minio-6").getByTestId("replication-host-progress")).toHaveAttribute(
|
||||
"value",
|
||||
/^67\.4$/
|
||||
);
|
||||
await expect(page.getByTestId("replication-host-database-7")).toContainText("mysql-replica-caught-up");
|
||||
await expect(page.getByTestId("replication-host-database-7").getByTestId("replication-host-progress")).toHaveCount(
|
||||
0
|
||||
);
|
||||
await expect(page.getByTestId("replication-host-minio-6").locator(".status-pill")).toContainText("Nedsat");
|
||||
await expect(page.getByTestId("replication-host-redis-4")).toContainText(
|
||||
"Venter på forbindelsen til den primære Redis-vært."
|
||||
);
|
||||
await expect(page.getByTestId("replication-host-minio-6")).toContainText("Synkroniserer i baggrunden.");
|
||||
await expect(page.getByTestId("replication-host-database-2")).toContainText(
|
||||
"Replikaen indeholder data i tabeller, hvor kun skemaet skal kopieres"
|
||||
);
|
||||
await expect(page.getByTestId("replication-host-database-2")).not.toContainText("Replica seed includes data");
|
||||
const primaryActions = page.getByTestId("replication-actions-database-1");
|
||||
const replicaActions = page.getByTestId("replication-actions-database-2");
|
||||
await expect(page.getByTestId("replication-host-database-2").locator(".buttons.are-small")).toHaveCount(0);
|
||||
await expect(primaryActions.locator(".action-settings-wheel-trigger")).toBeVisible();
|
||||
await primaryActions.locator(".action-settings-wheel-trigger").click();
|
||||
await expect(primaryActions.locator(".dropdown-item-action:visible")).toHaveCount(2);
|
||||
await expect(primaryActions.getByRole("button", { name: "Test" })).toBeVisible();
|
||||
await expect(primaryActions.getByRole("button", { name: "Omdøb" })).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(replicaActions.locator(".action-settings-wheel-trigger")).toBeVisible();
|
||||
const replicaActionTrigger = replicaActions.locator(".action-settings-wheel-trigger");
|
||||
await replicaActionTrigger.click();
|
||||
await expect(replicaActions.locator(".dropdown-item-action:visible")).toHaveCount(4);
|
||||
await expect(replicaActions.getByRole("button", { name: "Omdøb" })).toBeVisible();
|
||||
await getSettledActionMenuVerticalPlacement(
|
||||
page,
|
||||
replicaActionTrigger,
|
||||
replicaActions.locator(".dropdown-content")
|
||||
);
|
||||
await replicaActions.getByRole("button", { name: "Test" }).click();
|
||||
expect(replicationRequests.hostTestKinds).toContain("database");
|
||||
const addHostTabs = page.getByTestId("replication-add-host-tabs");
|
||||
await expect(addHostTabs).toContainText("Database");
|
||||
await expect(addHostTabs).toContainText("Redis");
|
||||
await expect(addHostTabs).toContainText("MinIO");
|
||||
const databasePanel = page.getByTestId("replication-add-database");
|
||||
await expect(databasePanel).toBeVisible();
|
||||
await expect(page.getByTestId("replication-compose-generator")).toContainText("Generér Docker Compose");
|
||||
await databasePanel.getByRole("button", { name: "Generér compose" }).click();
|
||||
await expect(page.getByTestId("replication-compose-result").locator("textarea").first()).toHaveValue(
|
||||
/mariadb-replica-1/
|
||||
);
|
||||
await expect(page.locator('input[type="password"]').nth(0)).toHaveValue("generated-app-password");
|
||||
await expect(page.getByTestId("replication-compose-result").locator("textarea").nth(2)).toHaveValue(/mariadb-dump/);
|
||||
await expect(page.getByTestId("replication-compose-result")).toContainText("Brug et unikt server-id");
|
||||
await databasePanel.getByLabel("Vært").first().fill("db.example.test");
|
||||
await databasePanel.getByRole("button", { name: "Test forbindelse" }).click();
|
||||
await expect(page.getByTestId("replication-credential-test-result")).toContainText(
|
||||
"Forbindelsen blev testet korrekt."
|
||||
);
|
||||
await expect(databasePanel.getByRole("button", { name: "Gem og klargør" })).toBeVisible();
|
||||
await databasePanel.getByRole("button", { name: "Gem og klargør" }).click();
|
||||
await expect(page.getByTestId("replication-add-database")).toContainText("Replikaen er allerede sikkert klargjort");
|
||||
const databaseCountsAfterDatabaseFlow = {
|
||||
credentialTests: replicationRequests.credentialTestKinds.filter((kind) => kind === "database").length,
|
||||
adds: replicationRequests.addKinds.filter((kind) => kind === "database").length,
|
||||
provisions: replicationRequests.provisionKinds.filter((kind) => kind === "database").length,
|
||||
};
|
||||
await addHostTabs.locator(".tabs").getByText("Redis", { exact: true }).click();
|
||||
const redisPanel = page.getByTestId("replication-add-redis");
|
||||
await expect(redisPanel).toBeVisible();
|
||||
await expect(page.getByTestId("replication-redis-compose-generator")).toContainText("Generér Docker Compose");
|
||||
await redisPanel.getByRole("button", { name: "Generér compose" }).click();
|
||||
await expect(page.getByTestId("replication-redis-compose-result").locator("textarea").first()).toHaveValue(
|
||||
/redis-replica-1/
|
||||
);
|
||||
await expect(page.getByTestId("replication-redis-compose-result").locator("textarea").first()).toHaveValue(
|
||||
/\/data\/redis\.conf/
|
||||
);
|
||||
await expect(page.getByTestId("replication-redis-compose-result").locator("textarea").nth(1)).toHaveValue(
|
||||
/REDIS_PASSWORD=generated-redis-password/
|
||||
);
|
||||
await expect(redisPanel.locator('input[type="password"]')).toHaveValue("generated-redis-password");
|
||||
await redisPanel.getByLabel("Vært", { exact: true }).fill("redis-replica.example.test");
|
||||
await redisPanel.getByRole("button", { name: "Test forbindelse" }).click();
|
||||
await expect(page.getByTestId("replication-redis-credential-test-result")).toContainText(
|
||||
"Forbindelsen blev testet korrekt."
|
||||
);
|
||||
await expect(redisPanel.getByRole("button", { name: "Gem og klargør" })).toBeVisible();
|
||||
await redisPanel.getByRole("button", { name: "Gem og klargør" }).click();
|
||||
await addHostTabs.locator(".tabs").getByText("MinIO", { exact: true }).click();
|
||||
const minioPanel = page.getByTestId("replication-add-minio");
|
||||
await expect(minioPanel).toBeVisible();
|
||||
await expect(page.getByTestId("replication-minio-compose-generator")).toContainText("Generér Docker Compose");
|
||||
await minioPanel.getByRole("button", { name: "Generér compose" }).click();
|
||||
await expect(page.getByTestId("replication-minio-compose-result").locator("textarea").first()).toHaveValue(
|
||||
/minio-replica-1/
|
||||
);
|
||||
await expect(page.getByTestId("replication-minio-compose-result").locator("textarea").nth(1)).toHaveValue(
|
||||
/MINIO_ROOT_PASSWORD=generated-minio-secret/
|
||||
);
|
||||
await expect(minioPanel.locator('input[type="password"]')).toHaveValue("generated-minio-secret");
|
||||
await minioPanel.getByLabel("Vært", { exact: true }).fill("minio-replica.example.test");
|
||||
await minioPanel.getByRole("button", { name: "Test forbindelse" }).click();
|
||||
await expect(page.getByTestId("replication-minio-credential-test-result")).toContainText(
|
||||
"Forbindelsen blev testet korrekt."
|
||||
);
|
||||
await expect(minioPanel.getByRole("button", { name: "Gem og klargør" })).toBeVisible();
|
||||
const minioProvisionResponse = page.waitForResponse(/\/superuser\/replication\/minio\/99\/provision$/);
|
||||
await minioPanel.getByRole("button", { name: "Gem og klargør" }).click();
|
||||
await minioProvisionResponse;
|
||||
expect(replicationRequests.composeKinds).toContain("database");
|
||||
expect(replicationRequests.composeKinds).toContain("redis");
|
||||
expect(replicationRequests.composeKinds).toContain("minio");
|
||||
expect(replicationRequests.credentialTestKinds).toContain("database");
|
||||
expect(replicationRequests.credentialTestKinds).toContain("redis");
|
||||
expect(replicationRequests.credentialTestKinds).toContain("minio");
|
||||
expect(replicationRequests.addKinds).toContain("database");
|
||||
expect(replicationRequests.addKinds).toContain("redis");
|
||||
expect(replicationRequests.addKinds).toContain("minio");
|
||||
expect(replicationRequests.provisionKinds).toContain("database");
|
||||
expect(replicationRequests.provisionKinds).toContain("redis");
|
||||
expect(replicationRequests.provisionKinds).toContain("minio");
|
||||
expect(replicationRequests.credentialTestKinds.filter((kind) => kind === "database")).toHaveLength(
|
||||
databaseCountsAfterDatabaseFlow.credentialTests
|
||||
);
|
||||
expect(replicationRequests.addKinds.filter((kind) => kind === "database")).toHaveLength(
|
||||
databaseCountsAfterDatabaseFlow.adds
|
||||
);
|
||||
expect(replicationRequests.provisionKinds.filter((kind) => kind === "database")).toHaveLength(
|
||||
databaseCountsAfterDatabaseFlow.provisions
|
||||
);
|
||||
await expect(page.getByTestId("replication-host-redis-4")).toContainText(
|
||||
"Redis-replikaens forbindelse til den primære vært er ikke oppe."
|
||||
);
|
||||
await expect(page.getByTestId("replication-host-minio-6")).toContainText(
|
||||
"MinIO-replikaen er ikke indhentet endnu."
|
||||
);
|
||||
await expect(page).toHaveURL(/\/superuser$/);
|
||||
await expect(page.getByTestId("system-status-dashboard")).toBeVisible({ timeout: PAGE_READY_TIMEOUT });
|
||||
await expect(page.getByTestId("system-status-panel-infrastructure")).toBeVisible();
|
||||
await expect(page.getByTestId("status-card-database")).toContainText("truckwash");
|
||||
await expect(page.getByTestId("status-card-database-replication")).toContainText("100.0% replikering");
|
||||
});
|
||||
|
||||
test("@smoke superuser module cards keep long Danish status reasons below the header row", async ({ page }) => {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { mockApi, primeMockSession } from "./support/network.js";
|
||||
|
||||
const CUSTOMER_SELF_SERVE_PERMISSIONS = [
|
||||
"user",
|
||||
"list_own_department_selfserve_vehicle_conditions",
|
||||
"add_own_department_selfserve_vehicle_conditions",
|
||||
];
|
||||
|
||||
async function suppressVueDevtoolsOverlay(page) {
|
||||
await page.addInitScript(() => {
|
||||
const appendStyle = () => {
|
||||
@@ -133,7 +139,7 @@ test.describe("User MyWashStart mocked flow", () => {
|
||||
const requests = collectSelfServeRequests(page);
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: { customer_number: 12345679 },
|
||||
selfServe: true,
|
||||
});
|
||||
@@ -177,7 +183,7 @@ test.describe("User MyWashStart mocked flow", () => {
|
||||
test("registration autocomplete does not cover vehicle type selection", async ({ page }) => {
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: { customer_number: 12345679 },
|
||||
selfServe: true,
|
||||
});
|
||||
@@ -204,7 +210,7 @@ test.describe("User MyWashStart mocked flow", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: { customer_number: 12345679 },
|
||||
selfServe: {
|
||||
relayResponses: [{ success: false, data: { message: "Machine relay unavailable" } }],
|
||||
@@ -239,7 +245,7 @@ test.describe("User MyWashStart mocked flow", () => {
|
||||
const requests = collectSelfServeRequests(page);
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: { customer_number: 12345679 },
|
||||
selfServe: {
|
||||
inProgressByLaneId: {
|
||||
@@ -301,7 +307,7 @@ test.describe("User MyWashStart mocked flow", () => {
|
||||
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: { customer_number: 12345679 },
|
||||
selfServe: {
|
||||
inProgressByLaneId: {
|
||||
@@ -325,7 +331,7 @@ test.describe("User MyWashStart mocked flow", () => {
|
||||
test("answer-sync failure shows an error and blocks progression", async ({ page }) => {
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: { customer_number: 12345679 },
|
||||
selfServe: true,
|
||||
});
|
||||
@@ -360,7 +366,7 @@ test.describe("User MyWashStart mocked flow", () => {
|
||||
const requests = collectSelfServeRequests(page);
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["user"],
|
||||
permissions: CUSTOMER_SELF_SERVE_PERMISSIONS,
|
||||
sessionData: { customer_number: 12345679 },
|
||||
selfServe: true,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user