test(e2e): reconcile shell and smoke consumers with approved lifetime and locale contracts

This commit is contained in:
cesnimda
2026-10-21 12:48:44 +02:00
parent 32e464f612
commit 508d68e16d
5 changed files with 316 additions and 12 deletions
@@ -0,0 +1,19 @@
# Integrated shell and smoke consumers
## Complete failed run and bounded correction
The WAL-source ordinary suite completed06:05:40–07:01:53UTC with **343PASS/8FAIL/0skipped/0flaky**, zero configured retries. SQLite Audit/language persistence and populated Users pagination now pass. Ordinary P2-D01 passes as an unchanged case. The eight remaining failures are retained in [portable evidence](evidence/terminal-consumers.json); this completed failed run is not reported as acceptance.
Four shell tooltip/toast cases waited for a private account export after navigating away from Backup. The [approved P7-09 lifetime boundary](../p7-09/DATA_PRIVACY_BACKUP.md) intentionally disposes that lazy owner on Settings-tab changes, preventing late private downloads/toasts. The existing four unit lifetime cases and native disposal case already enforce that behavior. Removing the guard to satisfy this older shell scenario would violate that acceptance contract.
The shell consumer therefore completes its real account ZIP while Backup stays mounted, then checks the enabled encrypted-backup action beneath the live success toast. All three widths, four theme/language pairs, actual ZIP filename, real first server response, shared2/minute export admission, focus/exposure, normal toast timing, screenshots, tooltip/axe and dismiss assertions remain. The existing response reuse across viewport samples stays unchanged. No product download, owner/session guard or notification implementation changes. Navigation and disposal remain covered in their responsible Settings tests.
The four smoke failures share a bootstrap account left in Bokmål by preceding bilingual cases. Local storage alone cannot override the persisted account preference. The existing root UI/API login helpers now explicitly persist English with real CSRF/session headers, asserting204. UI login still enters credentials and proves its redirect, then reloads the persisted-language result before English workflow assertions. Other accounts and bilingual tests are unchanged. `Career Workspace` remains the correct approved English heading; it is not renamed to make the test pass. The version-badge selector now uses the current semantic `img` role with the original exact accessible name; its version/commit content assertions remain.
## Results and limits
All **8affected native cases PASS**,10:44:02–10:45:21UTC, with0skips/flaky/retries. This composition deliberately runs the four language/theme shell cases before the four smoke workflows. Browser TypeScript and lint pass10:45:57–10:46:22UTC. No cases are added or removed from351; no timeout, authorization, persistence, route, draft or save assertion is relaxed.
Fresh whole frontend remains899/101PASS,0pending/failures,06:10:48–06:15:42UTC. Offline sidecar56PASS10:45:23–10:45:32UTC with model loading disabled. The unchanged fidelity oracle verifies the newly recorded standard16fixtures/36preview-PDF pages and Settings8fixtures/24pages10:47:16–10:47:30UTC. Stale earlier aggregate reports were archived, not reused: the Settings inputs are the four actual new locale/theme measurement files. Tolerances/oracles/renderer are unchanged.
The full351-case successor and separate lanes still follow. An overlapping8-case pass is not added to343to manufacture a completed full-suite pass. Product runtime inputs and Release assemblies retain the [WAL source identity](evidence/wal-entry.json); only these two consumer files changed after the completed failed run. All57group qualifications, vendor localisation residuals, human/device/output/provider/release requirements, P5-13 withholding and Phase8 boundary remain.
@@ -0,0 +1,104 @@
{
"schemaVersion": 1,
"checkpoint": "p7-12",
"status": "PASS",
"checkedUtc": "2026-10-21T10:48:38.452513+00:00",
"commands": [
{
"name": "secret-scan",
"command": [
"rtk",
"proxy",
"C:\\Users\\Connor\\AppData\\Local\\Programs\\Python\\Python312\\python.exe",
"-X",
"utf8",
"scripts/supply-chain.py",
"scan-secrets"
],
"startedUtc": "2026-10-21T10:48:34.965434+00:00",
"finishedUtc": "2026-10-21T10:48:37.641222+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261021T104834Z/secret-scan.log",
"logSha256Raw": "be5119e6057287d397e87759f855c91e634e3bfac61c8be7584d05592ba2dfcd"
},
{
"name": "working-whitespace",
"command": [
"rtk",
"proxy",
"git",
"diff",
"--check"
],
"startedUtc": "2026-10-21T10:48:37.641726+00:00",
"finishedUtc": "2026-10-21T10:48:37.726433+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261021T104834Z/working-whitespace.log",
"logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "staged-whitespace",
"command": [
"rtk",
"proxy",
"git",
"diff",
"--cached",
"--check"
],
"startedUtc": "2026-10-21T10:48:37.726433+00:00",
"finishedUtc": "2026-10-21T10:48:37.810579+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261021T104834Z/staged-whitespace.log",
"logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "phase7-whitespace",
"command": [
"rtk",
"proxy",
"git",
"diff",
"--check",
"b5319981ad3d1bd635daff861453d23c0de5320b"
],
"startedUtc": "2026-10-21T10:48:37.810579+00:00",
"finishedUtc": "2026-10-21T10:48:38.032817+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261021T104834Z/phase7-whitespace.log",
"logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
],
"untrackedSecretScan": {
"files": 0,
"findings": 0
},
"documentation": {
"files": 47,
"relativeLinks": 708,
"errors": []
},
"preservation": {
"groups": 57,
"statuses": {
"PARTIAL": 56,
"PASS": 0,
"FAIL": 0,
"DEFERRED": 1
},
"originalRoutesPreserved": true,
"currentConcreteRoutes": 33
},
"production": {
"main": "5f835e54a9b74e6b71418422e28431b2af06453f",
"originMain": "5f835e54a9b74e6b71418422e28431b2af06453f",
"status": "NO-GO",
"qualification": "Local fetched refs; direct push observation recorded separately."
},
"hashPolicy": "Portable JSON UTF8 LF; raw log hashes measure exact saved bytes.",
"limits": [
"Pattern scanner skips binary and files over2MiB; not historical Git/production secrets.",
"Local links check target existence, not URL/heading anchors.",
"Static evidence is not runtime/manual/device/provider/production acceptance."
]
}
@@ -0,0 +1,176 @@
{
"observedUtc": "2026-10-21T10:48:08.631477+00:00",
"failedCompleteRun": {
"stats": {
"startTime": "2026-10-21T06:05:41.420Z",
"duration": 3371710.188,
"expected": 343,
"skipped": 0,
"unexpected": 8,
"flaky": 0
},
"command": {
"name": "main",
"command": [
"rtk",
"proxy",
"node",
"node_modules/@playwright/test/cli.js",
"test",
"--retries=0",
"--reporter=list,json",
"--output=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\ui\\main-results"
],
"startedUtc": "2026-10-21T06:05:40.915843+00:00",
"finishedUtc": "2026-10-21T07:01:53.642856+00:00",
"exitCode": 1,
"failureLog": "E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\ui\\main.log",
"stats": {
"startTime": "2026-10-21T06:05:41.420Z",
"duration": 3371710.188,
"expected": 343,
"skipped": 0,
"unexpected": 8,
"flaky": 0
}
},
"raw": [
{
"path": ".artifacts/phase7/current/ui/main.json",
"sha256": "c5c2381f12572d07b63f39b54af0e8e19679576372f4c9009c921eca1f82fb7e"
},
{
"path": ".artifacts/phase7/current/ui/main.log",
"sha256": "9cf5784d7480d229303971f75897344358c2c86740f654a13ebe2b54244b843f"
},
{
"path": ".artifacts/phase7/current/ui/main-command.json",
"sha256": "ba09d8604684b77fd44896bd1087c9bf69793731b9437121519b9220fcbf902a"
}
],
"failures": [
{
"file": "shell-readiness.spec.ts",
"title": "[P5-00 PRES-E08 C06 S05] light-en real Tooltip contrast and open-toast action access",
"errors": [
"\u001b[31mTest timeout of 120000ms exceeded.\u001b[39m",
"Error: page.waitForEvent: Test timeout of 120000ms exceeded.\n=========================== logs ===========================\nwaiting for event \"download\"\n============================================================\n\n 335 | await route.fulfill({ response: exported });\n 336 | }, { times: 1 });\n> 337 | const download = page.waitForEvent(\"download\");\n | ^\n 338 | await main.getByRole(\"button\", { name: t.accountExportDownload, exact: true }).click();\n 339 | await choose(t.settingsTabGeneral);\n 340 | const manage = main.getByRole(\"button\", { name: t.settingsManageConnectedAccounts, exact: true });\n at E:\\Storage\\Projects\\Active\\Job tracker\\job-tracker-ui\\e2e\\shell-readiness.spec.ts:337:29"
]
},
{
"file": "shell-readiness.spec.ts",
"title": "[P5-00 PRES-E08 C06 S05] light-nb real Tooltip contrast and open-toast action access",
"errors": [
"\u001b[31mTest timeout of 120000ms exceeded.\u001b[39m",
"Error: page.waitForEvent: Test timeout of 120000ms exceeded.\n=========================== logs ===========================\nwaiting for event \"download\"\n============================================================\n\n 335 | await route.fulfill({ response: exported });\n 336 | }, { times: 1 });\n> 337 | const download = page.waitForEvent(\"download\");\n | ^\n 338 | await main.getByRole(\"button\", { name: t.accountExportDownload, exact: true }).click();\n 339 | await choose(t.settingsTabGeneral);\n 340 | const manage = main.getByRole(\"button\", { name: t.settingsManageConnectedAccounts, exact: true });\n at E:\\Storage\\Projects\\Active\\Job tracker\\job-tracker-ui\\e2e\\shell-readiness.spec.ts:337:29"
]
},
{
"file": "shell-readiness.spec.ts",
"title": "[P5-00 PRES-E08 C06 S05] dark-en real Tooltip contrast and open-toast action access",
"errors": [
"\u001b[31mTest timeout of 120000ms exceeded.\u001b[39m",
"Error: page.waitForEvent: Test timeout of 120000ms exceeded.\n=========================== logs ===========================\nwaiting for event \"download\"\n============================================================\n\n 335 | await route.fulfill({ response: exported });\n 336 | }, { times: 1 });\n> 337 | const download = page.waitForEvent(\"download\");\n | ^\n 338 | await main.getByRole(\"button\", { name: t.accountExportDownload, exact: true }).click();\n 339 | await choose(t.settingsTabGeneral);\n 340 | const manage = main.getByRole(\"button\", { name: t.settingsManageConnectedAccounts, exact: true });\n at E:\\Storage\\Projects\\Active\\Job tracker\\job-tracker-ui\\e2e\\shell-readiness.spec.ts:337:29"
]
},
{
"file": "shell-readiness.spec.ts",
"title": "[P5-00 PRES-E08 C06 S05] dark-nb real Tooltip contrast and open-toast action access",
"errors": [
"\u001b[31mTest timeout of 120000ms exceeded.\u001b[39m",
"Error: page.waitForEvent: Test timeout of 120000ms exceeded.\n=========================== logs ===========================\nwaiting for event \"download\"\n============================================================\n\n 335 | await route.fulfill({ response: exported });\n 336 | }, { times: 1 });\n> 337 | const download = page.waitForEvent(\"download\");\n | ^\n 338 | await main.getByRole(\"button\", { name: t.accountExportDownload, exact: true }).click();\n 339 | await choose(t.settingsTabGeneral);\n 340 | const manage = main.getByRole(\"button\", { name: t.settingsManageConnectedAccounts, exact: true });\n at E:\\Storage\\Projects\\Active\\Job tracker\\job-tracker-ui\\e2e\\shell-readiness.spec.ts:337:29"
]
},
{
"file": "smoke.spec.ts",
"title": "login establishes an authenticated session",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: getByLabel('Application version e2e-verification, commit e2e1234')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m - waiting for getByLabel('Application version e2e-verification, commit e2e1234')\u001b[22m\n\n\n 118 | await expect(page.getByText(\"e2e@example.test\").first()).toBeVisible();\n 119 | await expect(page.getByTestId(\"admin-version-badge\")).toHaveText(\"ve2e-verification\");\n> 120 | await expect(page.getByLabel(\"Application version e2e-verification, commit e2e1234\")).toBeVisible();\n | ^\n 121 |\n 122 | await page.getByRole(\"button\", { name: \"Notifications\" }).click();\n 123 | await expect(page.getByLabel(\"Notifications panel\")).toBeVisible();\n at E:\\Storage\\Projects\\Active\\Job tracker\\job-tracker-ui\\e2e\\smoke.spec.ts:120:89"
]
},
{
"file": "smoke.spec.ts",
"title": "a saved job can be created through the reviewed UI flow",
"errors": [
"\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
"Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m - waiting for getByRole('button', { name: 'Add Job' })\u001b[22m\n\n\n 180 | await login(page);\n 181 | await page.goto(\"/jobs\");\n> 182 | await page.getByRole(\"button\", { name: \"Add Job\" }).click();\n | ^\n 183 | await page.getByRole(\"button\", { name: \"Enter details manually\" }).click();\n 184 | await page.getByRole(\"dialog\").getByRole(\"combobox\", { name: \"Company\" }).fill(`E2E Company ${suffix}`);\n 185 | await page.getByLabel(\"Job title\").fill(title);\n at E:\\Storage\\Projects\\Active\\Job tracker\\job-tracker-ui\\e2e\\smoke.spec.ts:182:55"
]
},
{
"file": "smoke.spec.ts",
"title": "the dedicated application workspace survives deep links, long data and unsaved navigation",
"errors": [
"\u001b[31mTest timeout of 60000ms exceeded.\u001b[39m",
"Error: locator.click: Test timeout of 60000ms exceeded.\nCall log:\n\u001b[2m - waiting for getByRole('tablist', { name: 'Workspace sections' }).getByRole('tab', { name: 'Application', exact: true })\u001b[22m\n\n\n 261 |\n 262 | const workspaceNav = page.getByRole(\"tablist\", { name: \"Workspace sections\" });\n> 263 | await workspaceNav.getByRole(\"tab\", { name: \"Application\", exact: true }).click();\n | ^\n 264 | await page.getByRole(\"tab\", { name: \"Answers & message draft\", exact: true }).click();\n 265 | await expect(page).toHaveURL(new RegExp(`/jobs/${job.id}\\\\?section=application&sub=answers$`));\n 266 | await page.getByLabel(\"Application answer\", { exact: true }).fill(\"A reviewed answer that must not be lost.\");\n at E:\\Storage\\Projects\\Active\\Job tracker\\job-tracker-ui\\e2e\\smoke.spec.ts:263:77"
]
},
{
"file": "smoke.spec.ts",
"title": "Career Workspace loads from the authenticated application shell",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: getByRole('heading', { name: 'Career Workspace' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m - waiting for getByRole('heading', { name: 'Career Workspace' })\u001b[22m\n\n\n 324 | await page.evaluate(() => window.localStorage.setItem(\"jobtracker.themeMode\", \"light\"));\n 325 | await page.goto(\"/career\");\n> 326 | await expect(page.getByRole(\"heading\", { name: \"Career Workspace\" })).toBeVisible();\n | ^\n 327 | await expect(page.getByRole(\"link\", { name: \"Open CV Builder\" })).toHaveAttribute(\"href\", \"/career/builder\");\n 328 |\n 329 | await page.setViewportSize({ width: 375, height: 900 });\n at E:\\Storage\\Projects\\Active\\Job tracker\\job-tracker-ui\\e2e\\smoke.spec.ts:326:73"
]
}
]
},
"correctedConsumerVector": {
"stats": {
"startTime": "2026-10-21T10:44:02.866Z",
"duration": 78218.83300000001,
"expected": 8,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"command": {
"name": "terminal-consumers",
"command": [
"rtk",
"proxy",
"node",
"node_modules/@playwright/test/cli.js",
"test",
"e2e/shell-readiness.spec.ts",
"e2e/smoke.spec.ts",
"--grep",
"real Tooltip contrast|login establishes|saved job can be created|dedicated application workspace|Career Workspace loads",
"--retries=0",
"--reporter=list,json",
"--output=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\ui\\terminal-consumers-results"
],
"startedUtc": "2026-10-21T10:44:02.342682+00:00",
"finishedUtc": "2026-10-21T10:45:21.120749+00:00",
"exitCode": 0
},
"raw": [
{
"path": ".artifacts/phase7/current/ui/terminal-consumers.json",
"sha256": "e5b1eac14270bb33ad0b29ebbbefe256481155afe8ddf983ac33e86649bd7afa"
},
{
"path": ".artifacts/phase7/current/ui/terminal-consumers.log",
"sha256": "d468adfe7ea83c594a3e5082b2516b7d90c3e603f2a93deb048942650d1fe59e"
},
{
"path": ".artifacts/phase7/current/ui/terminal-consumers-command.json",
"sha256": "6c35ccb9b3c264ce453d66840b7da6d2a2b67e13181b72dcf411cfbad780f4c3"
}
]
},
"ordinaryP2D01": [
{
"file": "redesign-preservation.spec.ts",
"title": "[PRES-W01 PRES-W05 PRES-W06 PRES-W07 PRES-W08] legacy workspace preserves frozen documents after current sources change",
"status": "passed",
"retry": 0
}
],
"postRunSourceObservation": {
"differencesFromWalEntry": [
"job-tracker-ui/e2e/shell-readiness.spec.ts",
"job-tracker-ui/e2e/smoke.spec.ts"
],
"qualification": "Both edits made after the completed failed run; tracked product inputs and API/test assemblies retain the WAL identity. A new clean source freeze and whole-suite execution follow publication."
},
"qualification": "343 passing cases plus an overlapping8-case vector are not reported as a completed351-case pass. No skipped/expected-failure cases, retries, timeout increases or product changes are introduced."
}
+6 -11
View File
@@ -323,33 +323,28 @@ for (const mode of ["light", "dark"] as const) for (const language of ["en", "nb
} }
}; };
await choose(t.settingsTabBackup); await choose(t.settingsTabBackup);
// Complete the real export after navigating back to the bottom action.
// Otherwise clicking General correctly dismisses the toast via click-away.
let release!: () => void;
const held = new Promise<void>(resolve => { release = resolve; });
await page.route(`${apiUrl}/export/account`, async route => { await page.route(`${apiUrl}/export/account`, async route => {
// One native ZIP, replayed for viewport checks to respect the 2/minute owner limit. // One native ZIP, replayed for viewport checks to respect the 2/minute owner limit.
exported ??= await route.fetch(); exported ??= await route.fetch();
expect(exported.status()).toBe(200); expect(exported.status()).toBe(200);
await held;
await route.fulfill({ response: exported }); await route.fulfill({ response: exported });
}, { times: 1 }); }, { times: 1 });
const download = page.waitForEvent("download"); const download = page.waitForEvent("download");
await main.getByRole("button", { name: t.accountExportDownload, exact: true }).click(); await main.getByRole("button", { name: t.accountExportDownload, exact: true }).click();
await choose(t.settingsTabGeneral);
const manage = main.getByRole("button", { name: t.settingsManageConnectedAccounts, exact: true });
await manage.focus();
release();
expect((await download).suggestedFilename()).toMatch(/\.zip$/); expect((await download).suggestedFilename()).toMatch(/\.zip$/);
// Keep the private-export owner mounted; leaving Backup intentionally disposes late downloads.
const encrypted = main.getByRole("button", { name: t.backupDownload, exact: true });
await expect(encrypted).toBeEnabled();
await encrypted.focus();
const toast = page.getByRole("alert").filter({ hasText: t.accountExportDownloaded }); const toast = page.getByRole("alert").filter({ hasText: t.accountExportDownloaded });
await expect(toast).toBeVisible(); await expect(toast).toBeVisible();
// Deliberately align at the viewport end: reserved page space alone does // Deliberately align at the viewport end: reserved page space alone does
// not stop native scrolling from placing an action underneath the toast. // not stop native scrolling from placing an action underneath the toast.
await manage.evaluate(node => node.scrollIntoView({ block: "end" })); await encrypted.evaluate(node => node.scrollIntoView({ block: "end" }));
// Hover uses Snackbar's normal pause behavior; timing is not shortened or mocked. // Hover uses Snackbar's normal pause behavior; timing is not shortened or mocked.
await toast.hover(); await toast.hover();
await toast.getByRole("button").focus(); await toast.getByRole("button").focus();
await exposed(manage); await exposed(encrypted);
await capture(page, info, `open-toast-${width}-${suffix}`); await capture(page, info, `open-toast-${width}-${suffix}`);
await expect(toast).toBeVisible(); await expect(toast).toBeVisible();
await exposed(toast.getByRole("button")); await exposed(toast.getByRole("button"));
+11 -1
View File
@@ -40,6 +40,10 @@ async function loginAs(page: Page, email: string, password: string) {
await page.getByLabel("Current password").fill(password); await page.getByLabel("Current password").fill(password);
await page.getByRole("button", { name: "Sign in", exact: true }).click(); await page.getByRole("button", { name: "Sign in", exact: true }).click();
await expect(page).toHaveURL(/\/dashboard$/); await expect(page).toHaveURL(/\/dashboard$/);
if (email === "e2e@example.test") {
await setEnglishPreference(page);
await page.reload();
}
await expect(page.getByRole("heading", { name: "Dashboard", exact: true })).toBeVisible(); await expect(page.getByRole("heading", { name: "Dashboard", exact: true })).toBeVisible();
} }
@@ -49,6 +53,7 @@ async function login(page: Page) {
async function loginForApiSetup(page: Page) { async function loginForApiSetup(page: Page) {
expect((await page.request.post(`${apiUrl}/auth/login`, { data: { email: "e2e@example.test", password: "E2ePassword123!" } })).status()).toBe(200); expect((await page.request.post(`${apiUrl}/auth/login`, { data: { email: "e2e@example.test", password: "E2ePassword123!" } })).status()).toBe(200);
await setEnglishPreference(page);
} }
async function csrfHeader(page: Page) { async function csrfHeader(page: Page) {
@@ -59,6 +64,11 @@ async function csrfHeader(page: Page) {
return { "X-CSRF-TOKEN": cookie!.value, "X-Session-Context": me.sessionContext }; return { "X-CSRF-TOKEN": cookie!.value, "X-Session-Context": me.sessionContext };
} }
async function setEnglishPreference(page: Page) {
const headers = await csrfHeader(page);
expect((await page.request.put(`${apiUrl}/auth/preferences/language`, { headers, data: { language: "en" } })).status()).toBe(204);
}
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.addInitScript(() => window.localStorage.setItem("uiLanguage", "en")); await page.addInitScript(() => window.localStorage.setItem("uiLanguage", "en"));
}); });
@@ -117,7 +127,7 @@ test("login establishes an authenticated session", async ({ page }) => {
await login(page); await login(page);
await expect(page.getByText("e2e@example.test").first()).toBeVisible(); await expect(page.getByText("e2e@example.test").first()).toBeVisible();
await expect(page.getByTestId("admin-version-badge")).toHaveText("ve2e-verification"); await expect(page.getByTestId("admin-version-badge")).toHaveText("ve2e-verification");
await expect(page.getByLabel("Application version e2e-verification, commit e2e1234")).toBeVisible(); await expect(page.getByRole("img", { name: "Application version e2e-verification, commit e2e1234", exact: true })).toBeVisible();
await page.getByRole("button", { name: "Notifications" }).click(); await page.getByRole("button", { name: "Notifications" }).click();
await expect(page.getByLabel("Notifications panel")).toBeVisible(); await expect(page.getByLabel("Notifications panel")).toBeVisible();