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:
Jeppe B
2026-08-15 20:51:20 +02:00
committed by GitHub
co-authored by Jeppe openhands
parent e2cc76091f
commit 0369664a96
2 changed files with 173 additions and 0 deletions
+1
View File
@@ -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();
});
});