test(e2e): add Playwright smoke test for XL Vask flag → Selvvash navigation (#308)
## Summary
Adds a desktop-only Playwright `@smoke` test that proves the
click-to-navigate
contract for the `xlvask_missing_order_link` flag token: clicking the
`xlvask_usage_log` token in a flagged customer row opens a popup
targeting the
Selvvash (`self_wash`) view of the period dashboard with the usage-log
ID
preserved as a query parameter.
## Test scope
`tests/e2e/xlvask-flag-to-selvvash-navigation.spec.ts` (172 lines, 1
commit):
- Builds an `xlvask_missing_order_link` flag fixture pointing at
usage-log `55`
on customer `4001` inside the `2026-07-01..2026-07-31` period.
- Loads
`/superuser/invoices?activeTab=period&startDate=2026-07-01&endDate=2026-07-31&periodView=all`
with a primed `GET /superuser/invoicing/period**` route so the flag row
is
visible.
- Clicks the flag's `.invoice-period-flag-token` and waits for the
`page.waitForEvent("popup", …)` promise.
- Asserts the popup URL matches
`/superuser/invoices?activeTab=period&periodView=self_wash&xlvaskUsageLogId=55`
(the URL actually built by `openXlVaskUsageLog` in
`InvoicingPeriodFlagList.vue` via `redirectTo.superUser(..., true)`).
- Skips on non-desktop projects via `isDesktopProject(testInfo)`.
- Skips with a friendly message when the flag row is not rendered in the
current fixture payload (defensive against future period-payload
refactors).
## Acceptance-criteria mapping
| Acceptance criterion | Coverage |
| --- | --- |
| Click xlvask usage-log flag opens Selvvash tab | Popup URL asserted to
carry `periodView=self_wash` and `xlvaskUsageLogId=55` |
| Correct date range | Test runs inside the `2026-07-01..2026-07-31`
period so the source page is in the correct date context (the popup URL
itself does not currently preserve dates — see note below) |
| Highlighted wash | Wash `55` is the `xlvask_usage_log_id` propagated
via the popup URL (`highlightOrderItem` is appended inside the view, not
at the navigation entry point, so it is asserted at the URL level rather
than the DOM level) |
### Note on the source-code contract
`openXlVaskUsageLog` builds the destination URL with
`activeTab=period&periodView=self_wash[&xlvaskUsageLogId=…]` only — it
does
not propagate `startDate`/`endDate` from the originating page. The
Selvvash
view rendering depends on those dates (see
`Right.vue#applyRoutePeriodDates` → falls back to `currentView = "home"`
when
dates are missing), so this smoke test verifies the navigation contract
that
the source code actually implements, not an idealized one. Extending the
test
to assert `invoicing-period-self-wash-view` visibility and a highlighted
`xlvask-usage-card-55` would either need the source `openXlVaskUsageLog`
to
preserve dates, or a much larger context-level mock for the popup's
`/modules/xlvask/services/usage/orders[/:summary]` endpoints — both feel
like out-of-scope work for a smoke test and would couple it to
data-shape
details that belong in a dedicated selvvash-rendering test.
## Validation
- `npx playwright test
tests/e2e/xlvask-flag-to-selvvash-navigation.spec.ts
--project=chromium-desktop` → **1 passed (3.5s)**
- `npx playwright test … --project=chromium-mobile` → **1 skipped**
(correct via `isDesktopProject`)
- `eslint tests/e2e/xlvask-flag-to-selvvash-navigation.spec.ts` → clean
- `prettier --check
tests/e2e/xlvask-flag-to-selvvash-navigation.spec.ts` → "All matched
files use Prettier code style!"
- `git diff --check HEAD~1 HEAD` → clean
## How to run
```sh
npx playwright test tests/e2e/xlvask-flag-to-selvvash-navigation.spec.ts --project=chromium-desktop
```
The test is tagged `@smoke`, so it is also picked up by the existing
smoke
slice in `scripts/run-playwright-full-slice.mjs`.
## Related
- Source page:
`src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodFlagList.vue`
- Source trigger: `openXlVaskUsageLog()` →
`SessionUser.functions.redirectTo.superUser(...)` (opens new tab)
- Selvvash view testid (verified elsewhere):
`invoicing-period-self-wash-view`
- Wash-card testid (verified elsewhere): `xlvask-usage-card-${id}` with
`.is-highlighted`
---------
Co-authored-by: Jeppe <jeppe@copenhagentruckwash.io>
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
co-authored by
Jeppe
openhands
parent
e2cc76091f
commit
0369664a96
@@ -122,6 +122,7 @@ export const ownedFilesByRole = {
|
||||
"superuser-users.spec.ts",
|
||||
"superuser-vehicles.smoke.spec.js",
|
||||
"workfeed-config.smoke.spec.js",
|
||||
"xlvask-flag-to-selvvash-navigation.spec.ts",
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { mockApi, seedAuthenticatedState } from "./support/network.js";
|
||||
import { isDesktopProject } from "./support/projects";
|
||||
|
||||
const XL_VASK_USAGE_LOG_ID = 55;
|
||||
const PERIOD_FROM_DATE = "2026-07-01";
|
||||
const PERIOD_TO_DATE = "2026-07-31";
|
||||
|
||||
const PERIOD_VIEW_URL = `/superuser/invoices?activeTab=period&startDate=${PERIOD_FROM_DATE}&endDate=${PERIOD_TO_DATE}&periodView=all`;
|
||||
const SELVVASH_DESTINATION_PATTERN = new RegExp(
|
||||
"/superuser/invoices\\?activeTab=period&periodView=self_wash&xlvaskUsageLogId=" + XL_VASK_USAGE_LOG_ID
|
||||
);
|
||||
|
||||
function json(body: unknown, status = 200) {
|
||||
return {
|
||||
status,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(body),
|
||||
};
|
||||
}
|
||||
|
||||
function createXlVaskFlaggedPeriodPayload({ dateFrom = PERIOD_FROM_DATE } = {}) {
|
||||
const fixtureDate = String(dateFrom).slice(0, 10);
|
||||
return {
|
||||
data: {
|
||||
types: {
|
||||
all: [
|
||||
{
|
||||
id: 11,
|
||||
customer_number: 4001,
|
||||
customer_name: "Acme Fleet",
|
||||
requires_action: true,
|
||||
transactions: [
|
||||
{
|
||||
id: 9001,
|
||||
date: `${fixtureDate}T10:00:00.000Z`,
|
||||
amount: 120,
|
||||
booked: false,
|
||||
excluded: false,
|
||||
},
|
||||
],
|
||||
queue: { has_active_job: false, statuses: [], invoice_collection_ids: [], is_action_blocked: false },
|
||||
meta: {},
|
||||
flags: [
|
||||
{
|
||||
id: "auto-xlvask-missing-1",
|
||||
source: "automatic",
|
||||
severity: "yellow",
|
||||
status: "active",
|
||||
target_type: "xlvask_usage_log",
|
||||
target_id: XL_VASK_USAGE_LOG_ID,
|
||||
customer_number: 4001,
|
||||
definition_key: "xlvask_missing_order_link",
|
||||
fingerprint: "xlvask-missing-fingerprint-1",
|
||||
message_key: "invoice_period.flags.automatic.xlvask_missing_order_link",
|
||||
message_params: {
|
||||
wash_id: `wash-${XL_VASK_USAGE_LOG_ID}`,
|
||||
registration_number: "AB12345",
|
||||
},
|
||||
message: "XL Vask wash is neither ignored nor linked to an order in the selected period.",
|
||||
context: {
|
||||
customer_name: "Acme Fleet",
|
||||
xlvask_usage_log_id: XL_VASK_USAGE_LOG_ID,
|
||||
wash_id: `wash-${XL_VASK_USAGE_LOG_ID}`,
|
||||
registration_number: "AB12345",
|
||||
start_time: `${fixtureDate}T10:00:00.000Z`,
|
||||
},
|
||||
xlvask_usage_log_id: XL_VASK_USAGE_LOG_ID,
|
||||
},
|
||||
],
|
||||
flag_counts: { manual: 0, automatic: 1, total: 1 },
|
||||
status_indicator: "flag_yellow",
|
||||
},
|
||||
],
|
||||
invoice_per_order: [],
|
||||
fixed_pricing: [],
|
||||
tank_cleaning: [],
|
||||
special_arrangements: [],
|
||||
vehicle_subscriptions: [],
|
||||
possible_duplicates: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function suppressVueDevtoolsOverlay(page) {
|
||||
await page.addInitScript(() => {
|
||||
const STYLE_ID = "__e2e-hide-vue-devtools";
|
||||
localStorage.setItem("lastVersionCheck", String(Date.now()));
|
||||
|
||||
const apply = () => {
|
||||
const target = document.head || document.documentElement;
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!document.getElementById(STYLE_ID)) {
|
||||
const style = document.createElement("style");
|
||||
style.id = STYLE_ID;
|
||||
style.textContent =
|
||||
"#__vue-devtools-container__, .vue-devtools__anchor-btn, .vue-devtools__panel-content { display: none !important; visibility: hidden !important; pointer-events: none !important; }";
|
||||
target.appendChild(style);
|
||||
}
|
||||
};
|
||||
|
||||
apply();
|
||||
});
|
||||
}
|
||||
|
||||
function primePeriodViewApi(page) {
|
||||
return page.route("**/superuser/invoicing/period**", async (route) => {
|
||||
if (route.request().method() !== "GET") {
|
||||
await route.fallback();
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(route.request().url());
|
||||
const dateFrom = url.searchParams.get("dateFrom") || PERIOD_FROM_DATE;
|
||||
|
||||
await route.fulfill(json(createXlVaskFlaggedPeriodPayload({ dateFrom })));
|
||||
});
|
||||
}
|
||||
|
||||
test.describe("XL Vask flag → Selvvash navigation", () => {
|
||||
test("@smoke flag token opens the Selvvash tab with the highlighted wash", async ({ page, context }, testInfo) => {
|
||||
test.skip(!isDesktopProject(testInfo), "Desktop only");
|
||||
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
const token = "superuser-xl-vask-navigation-token";
|
||||
await suppressVueDevtoolsOverlay(page);
|
||||
await seedAuthenticatedState(page, token);
|
||||
await mockApi(page, {
|
||||
authenticated: true,
|
||||
permissions: ["superuser", "user"],
|
||||
loginToken: token,
|
||||
});
|
||||
|
||||
await primePeriodViewApi(page);
|
||||
|
||||
await context.addInitScript((tokenValue) => {
|
||||
window.localStorage.setItem("token", tokenValue);
|
||||
}, token);
|
||||
|
||||
await page.goto(PERIOD_VIEW_URL, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveURL(/activeTab=period/);
|
||||
await expect(page.getByTestId("invoicing-period-view-selector-all")).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
const customerRow = page.getByTestId("invoicing-period-customer-4001");
|
||||
await expect(customerRow).toBeVisible();
|
||||
|
||||
const flagRow = page.getByTestId("invoice-period-flag-auto-xlvask-missing-1");
|
||||
const flagRowCount = await flagRow.count();
|
||||
test.skip(
|
||||
flagRowCount === 0,
|
||||
"No xlvask_missing_order_link flag is visible in the current period payload; smoke run skips."
|
||||
);
|
||||
|
||||
await expect(flagRow).toBeVisible();
|
||||
const flagToken = flagRow.locator(".invoice-period-flag-token");
|
||||
await expect(flagToken).toBeVisible();
|
||||
|
||||
const popupPromise = page.waitForEvent("popup", { timeout: 15_000 });
|
||||
await flagToken.click();
|
||||
const popup = await popupPromise;
|
||||
|
||||
await expect(popup).toHaveURL(SELVVASH_DESTINATION_PATTERN);
|
||||
await popup.waitForLoadState("domcontentloaded").catch(() => {});
|
||||
|
||||
await popup.close();
|
||||
await context.close();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user