diff --git a/docs/redesign/phase-7/p7-12/FIXTURE_ISOLATION.md b/docs/redesign/phase-7/p7-12/FIXTURE_ISOLATION.md new file mode 100644 index 0000000..2a83eed --- /dev/null +++ b/docs/redesign/phase-7/p7-12/FIXTURE_ISOLATION.md @@ -0,0 +1,26 @@ +# Shared native fixture isolation + +## Recorded failure + +The committed-source run on `b333ef55d5665223d2f570ec9681336fc3971c8a` completed20October19:15:16UTC with **103passed/248failed/0skipped/0flaky**, zero configured retries. This is a failed integrated run, retained in its raw JSON/log/trace evidence, not248 established application defects. + +Earlier zoom and route-census cases created synthetic administrators without removing their elevated roles. The Users case assumed the bootstrap administrator was the last one, attempted self-demotion, received204instead of409 and consequently removed the shared fixture's administrative access. Most later setup requests returned403. No server authorization failure is established: demotion was permitted because other administrators existed. + +Separately, Audit’s bilingual samples persisted Bokmål to the shared administrator. System’s English fixture seeded only localStorage; the application correctly hydrated authoritative `/auth/me` language and displayed `Prøv igjen`/`Innstillinger`, while the test expected English. Trace responses and accessible snapshots confirm this cause. + +An earlier Audit GET of the restored owned job328 returned500. It does not follow from later self-demotion. A read-only SQLite backup into an isolated diagnostic data root and an authenticated GET returned200; the combined successor case also passes. Root cause remains unproven. Do not close the intermittent read debt from these successful continuations. The ordinary browser backend now pipes Warning/error logs so future500responses retain server diagnostics without verbose SQL logging. + +## Bounded correction and preservation + +- Zoom and route census retain the exact ID of their own created administrator, remove only that synthetic account's roles in `finally` using bootstrap credentials, assert204and still dispose contexts/attach evidence on cleanup failure. No account or database is deleted. +- Users first asserts the bootstrap is the sole administrator before attempting self-demotion. This prevents another future fixture leak from causing a cascading loss of test authority. Its original409, ordinary-user403, session revocation and deletion-disabled503assertions remain. Its promoted synthetic account is demoted in `finally` through the independent bootstrap context. +- Audit, System and Users bootstrap login helpers explicitly save the language required by their fixture through the existing protected API. The bilingual cases retain every explicit per-sample update and assertion. Ordinary owners are unaffected. +- No application/API/schema/security semantics, production data, model, renderer, test timeout, skip, expected-failure marker or configured retry is changed. No case is removed or reordered to hide the integration failure. + +## Successor evidence + +The combined ordinary run executes both zoom cases, all five route/Board cases, Audit3, System3and Users3in their normal order on one fresh fixture: **16/16PASS**,21October05:11:01–05:15:20UTC, zero skips/flaky/retries. This proves producers and consumers compose, including the last-admin precondition and original409. Raw artifacts are `.artifacts/phase7/current/ui/composition.json`, `composition.log` and `composition-results/`; the portable manifest records hashes. + +Two earlier composition attempts correctly refused to reuse port5302left by the parent's isolated diagnostic process. That exact task-owned process was identified and stopped; neither failed setup is a passing run. Source collection is post-run; the late stdout/log-level diagnostic change is qualified separately and receives fresh type/lint/full-run proof. + +Case inventories remain899frontend/101suites,1057backend,56sidecar and351ordinary browser. This repair adds0cases; the preceding confirmation repair accounts for the899th frontend case. Overlapping focused vectors are not summed. Full integrated successor execution and final post-run source reconciliation remain required. All57 PRES dispositions and33routes remain; production readiness is NO-GO, P5-13 withheld and Phase8 not started. diff --git a/docs/redesign/phase-7/p7-12/README.md b/docs/redesign/phase-7/p7-12/README.md index 40cbb86..b3499c6 100644 --- a/docs/redesign/phase-7/p7-12/README.md +++ b/docs/redesign/phase-7/p7-12/README.md @@ -1,5 +1,11 @@ # P7-12 — Integrated migration acceptance +## Continuation — 21 October + +The first complete successor run failed103/351, with248unexpected failures and zero skips/flaky/retries; it is not accepted. [Fixture investigation](FIXTURE_ISOLATION.md) and [portable evidence](evidence/fixture-repair.json) separate administrative role/language contamination from an unresolved isolated Audit500. The corrected shared-fixture composition passes16/16. Existing409/403/503, ownership, source/save and route assertions remain; no test is removed or relaxed. The final whole run and separate lanes still follow. The earlier18:52:58UTC clean publication of entry evidence `ddb9c3da4ff31447fa6bd579002f32199052937d` remains historical, not final acceptance. + +Separately, parser native-exit diagnostics `dda16873455259bce23a744bd838c3e3e0fc6ee8` are pushed on development and equivalent main8699ae4 is normally pushed/directly verified21October05:13:39UTC. Release837 remains unqualified: it subsequently failed in native MSBuild/.NET test compilation after the parser process succeeded. No deployment/model activation is claimed. The [parser report](../../../infrastructure/2026-10-21-parser-native-exit.md) preserves the earlier distinct SIGSEGV and diagnostic-only scope. + ## Entry and bounded scope All P7-00–P7-11 presentation boundaries are implemented and published, with their original qualifications retained. P7-11 final error-owner commit `584530b694a2238928d10fddfc013499cf9b3fab` was pushed and directly verified clean at 2026-10-20T18:29:28 UTC. The normal main-repair reconciliation merge `8bbaf1f875f6d32dd7d379779a35188dca844fbb` was pushed and verified clean at18:29:38 UTC. These receipts supersede earlier pending-publication observations. diff --git a/docs/redesign/phase-7/p7-12/evidence/fixture-repair.json b/docs/redesign/phase-7/p7-12/evidence/fixture-repair.json new file mode 100644 index 0000000..be99208 --- /dev/null +++ b/docs/redesign/phase-7/p7-12/evidence/fixture-repair.json @@ -0,0 +1,75 @@ +{ + "observedUtc": "2026-10-21T05:18:18.993008+00:00", + "failure": { + "stats": { + "startTime": "2026-10-20T18:50:55.722Z", + "duration": 1460403.5180000002, + "expected": 103, + "skipped": 0, + "unexpected": 248, + "flaky": 0 + }, + "rawJsonSha256": "aef0c669476135ab655a30104bba9886cfe5fb64458285dd7a2be4254d1e5343", + "rawLogSha256": "f0280b5a9665af14952c583b3fab2af10d330574a0f48e25201e44f7e2fe5a27", + "source": "b333ef55d5665223d2f570ec9681336fc3971c8a", + "qualification": "Completed failed whole run. Successor runner archives these exact raw bytes under ui/attempts; final manifest must resolve the archive path by hash. No failure is converted to a skip or expected failure." + }, + "composition": { + "stats": { + "startTime": "2026-10-21T05:11:01.774Z", + "duration": 259024.46, + "expected": 16, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "command": { + "name": "composition", + "command": [ + "rtk", + "proxy", + "node", + "node_modules/@playwright/test/cli.js", + "test", + "e2e/browser-zoom-audit.spec.ts", + "e2e/functional-acceptance.spec.ts", + "e2e/phase7-admin-audit.spec.ts", + "e2e/phase7-admin-system.spec.ts", + "e2e/phase7-admin-users.spec.ts", + "--retries=0", + "--reporter=list,json", + "--output=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\ui\\composition-results" + ], + "startedUtc": "2026-10-21T05:11:01.254030+00:00", + "finishedUtc": "2026-10-21T05:15:20.899324+00:00", + "exitCode": 0 + }, + "path": ".artifacts/phase7/current/ui/composition.json", + "sha256": "e751f72a84bbde8340b4077782c415cae62d0d175ad3684766366a428c1b3d48" + }, + "files": { + "job-tracker-ui/e2e/browser-zoom-audit.spec.ts": "d2e14c8fb86a068456c1bcebf090b707d6448561b8643f13204bcd22e1515b37", + "job-tracker-ui/e2e/functional-acceptance.spec.ts": "fbba35f4ccdae767e5a4d87623f190bb348ce3ecd1eb7a4248316d6822fa3dd0", + "job-tracker-ui/e2e/phase7-admin-audit.spec.ts": "3737f20446d56bc6e61ba249b2c13316314aa0bfde6e7ff2202a781d3f793d6f", + "job-tracker-ui/e2e/phase7-admin-system.spec.ts": "1e4dc4a33587adcc8f2f4cc23f83a2d7f47b589d0fa4b81977187d5849dbce37", + "job-tracker-ui/e2e/phase7-admin-users.spec.ts": "df6ca26e3608a7dd0772f7dbcb71258fa7d58e7535982ecd5eddec157fcc790c", + "job-tracker-ui/playwright.config.ts": "24f13510ab8d4f2718bfeaaa7c77bb95ae109c69d4a318abd1b5c87777306ee0" + }, + "axe": { + "scans": 28, + "violations": 0, + "incompleteRules": { + "color-contrast": 2145, + "aria-prohibited-attr": 10 + }, + "incompleteNodes": 2155 + }, + "visualEvidence": "Screenshots are retained automated state evidence; no new human image review claimed for this fixture-only repair. Prior checkpoint visual inspections remain qualified.", + "sourceQualification": "Post-run source collection. Logging-only Playwright stdout/log-level change was made while composition was already running and did not affect that backend process; fresh successor full run validates it.", + "conditions": [ + "Audit owned-read500 remains unreproduced/intermittent, not closed by successful rerun.", + "Role cleanup is finally-based; sole-admin precondition fails safely if forced termination prevents earlier cleanup.", + "Axe incomplete contexts remain raw triage evidence and do not establish human screen-reader/device acceptance.", + "Full351browser and separate HTTP/security lanes remain required; no final acceptance yet." + ] +} diff --git a/docs/redesign/phase-7/p7-12/evidence/fixture-static-checks.json b/docs/redesign/phase-7/p7-12/evidence/fixture-static-checks.json new file mode 100644 index 0000000..565a50b --- /dev/null +++ b/docs/redesign/phase-7/p7-12/evidence/fixture-static-checks.json @@ -0,0 +1,104 @@ +{ + "schemaVersion": 1, + "checkpoint": "p7-12", + "status": "PASS", + "checkedUtc": "2026-10-21T05:21:16.714740+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-21T05:21:11.487147+00:00", + "finishedUtc": "2026-10-21T05:21:15.631000+00:00", + "exitCode": 0, + "log": ".artifacts/phase7/current/static/20261021T052111Z/secret-scan.log", + "logSha256Raw": "be5119e6057287d397e87759f855c91e634e3bfac61c8be7584d05592ba2dfcd" + }, + { + "name": "working-whitespace", + "command": [ + "rtk", + "proxy", + "git", + "diff", + "--check" + ], + "startedUtc": "2026-10-21T05:21:15.631500+00:00", + "finishedUtc": "2026-10-21T05:21:15.721641+00:00", + "exitCode": 0, + "log": ".artifacts/phase7/current/static/20261021T052111Z/working-whitespace.log", + "logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "staged-whitespace", + "command": [ + "rtk", + "proxy", + "git", + "diff", + "--cached", + "--check" + ], + "startedUtc": "2026-10-21T05:21:15.722142+00:00", + "finishedUtc": "2026-10-21T05:21:15.811199+00:00", + "exitCode": 0, + "log": ".artifacts/phase7/current/static/20261021T052111Z/staged-whitespace.log", + "logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "phase7-whitespace", + "command": [ + "rtk", + "proxy", + "git", + "diff", + "--check", + "b5319981ad3d1bd635daff861453d23c0de5320b" + ], + "startedUtc": "2026-10-21T05:21:15.811199+00:00", + "finishedUtc": "2026-10-21T05:21:16.134813+00:00", + "exitCode": 0, + "log": ".artifacts/phase7/current/static/20261021T052111Z/phase7-whitespace.log", + "logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ], + "untrackedSecretScan": { + "files": 0, + "findings": 0 + }, + "documentation": { + "files": 42, + "relativeLinks": 668, + "errors": [] + }, + "preservation": { + "groups": 57, + "statuses": { + "PARTIAL": 56, + "PASS": 0, + "FAIL": 0, + "DEFERRED": 1 + }, + "originalRoutesPreserved": true, + "currentConcreteRoutes": 33 + }, + "production": { + "main": "8699ae4f421bb1d049b937e735e40a4797a4b719", + "originMain": "8699ae4f421bb1d049b937e735e40a4797a4b719", + "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." + ] +} diff --git a/job-tracker-ui/e2e/browser-zoom-audit.spec.ts b/job-tracker-ui/e2e/browser-zoom-audit.spec.ts index fc8093c..d016ba5 100644 --- a/job-tracker-ui/e2e/browser-zoom-audit.spec.ts +++ b/job-tracker-ui/e2e/browser-zoom-audit.spec.ts @@ -61,6 +61,8 @@ for (const [language, mode] of [['en', 'light'], ['nb', 'dark']] as const) { args: ['--window-size=1280,900', `--disable-extensions-except=${extension}`, `--load-extension=${extension}`], }); const admin = await playwright.request.newContext(); + let adminHeaders: Record | undefined; + let createdAdminId: string | undefined; const records: unknown[] = []; try { await context.route('**/*', route => { @@ -98,9 +100,12 @@ for (const [language, mode] of [['en', 'light'], ['nb', 'dark']] as const) { await zoom(factor); records.push({ area: 'Authentication', route: '/login', language, mode, zoom: factor, geometry: await geometry(page) }); } - const adminHeaders = await login(admin, 'e2e@example.test'); + adminHeaders = await login(admin, 'e2e@example.test'); const email = `zoom-${language}-${Date.now()}@example.test`; - expect((await admin.post(`${apiUrl}/users`, { headers: adminHeaders, data: { email, password, roles: ['Admin'] } })).status()).toBe(200); + const created = await admin.post(`${apiUrl}/users`, { headers: adminHeaders, data: { email, password, roles: ['Admin'] } }); + expect(created.status()).toBe(200); + createdAdminId = (await created.json()).id; + expect(createdAdminId).toEqual(expect.any(String)); const headers = await login(page.request, email); expect((await page.request.put(`${apiUrl}/auth/preferences/language`, { headers, data: { language: language === 'nb' ? 'nb-NO' : 'en' } })).status()).toBe(204); const profile = { version: '1', contact: { fullName: 'Åse Zoom Fixture', headline: 'Documented application workflows', location: 'Oslo' }, summary: ['Supported source summary.'], skills: ['C#', 'React'], jobs: [{ id: 'zoom-role', title: 'Engineer', company: 'Synthetic company', bullets: ['Built and maintained internal web applications.'] }] }; @@ -153,9 +158,13 @@ for (const [language, mode] of [['en', 'light'], ['nb', 'dark']] as const) { expect(await (await page.request.get(`${apiUrl}/career/profile`)).json()).toEqual(source); expect(await (await page.request.get(`${apiUrl}/cv/variants/${cv.id}`)).json()).toEqual(cv); } finally { - await info.attach('actual-browser-zoom-observations', { body: JSON.stringify({ method: 'chrome.tabs.setZoom; viewport:null; dpr and actual zoom asserted; not human acceptance', browser: context.browser()?.version(), records }, null, 2), contentType: 'application/json' }); - await admin.dispose(); - await context.close(); + try { + if (createdAdminId) expect((await admin.put(`${apiUrl}/users/${createdAdminId}/roles`, { headers: adminHeaders, data: { roles: [] } })).status()).toBe(204); + } finally { + await info.attach('actual-browser-zoom-observations', { body: JSON.stringify({ method: 'chrome.tabs.setZoom; viewport:null; dpr and actual zoom asserted; not human acceptance', browser: context.browser()?.version(), records }, null, 2), contentType: 'application/json' }); + await admin.dispose(); + await context.close(); + } } }); } diff --git a/job-tracker-ui/e2e/functional-acceptance.spec.ts b/job-tracker-ui/e2e/functional-acceptance.spec.ts index cb80349..449c898 100644 --- a/job-tracker-ui/e2e/functional-acceptance.spec.ts +++ b/job-tracker-ui/e2e/functional-acceptance.spec.ts @@ -90,10 +90,18 @@ for (const identity of ['anonymous', 'normal', 'admin'] as const) { test(`[P5-15-01 E02 E07 A01] all 33 routes retain native entry, refresh and ${identity} permissions`, async ({ page, playwright }, info) => { test.setTimeout(300_000); const admin = await playwright.request.newContext(); + let adminHeaders: Record | undefined; + let createdAdminId: string | undefined; const evidence: { pattern: string; mode: string; destination: string; status?: number }[] = []; try { - const adminHeaders = await login(admin, 'e2e@example.test'); - const email = await createAccount(admin, adminHeaders, identity === 'admin' ? ['Admin'] : []); + adminHeaders = await login(admin, 'e2e@example.test'); + const email = `acceptance-${Date.now()}-${Math.random().toString(36).slice(2)}@example.test`; + const created = await admin.post(`${apiUrl}/users`, { headers: adminHeaders, data: { email, password, roles: identity === 'admin' ? ['Admin'] : [] } }); + expect(created.status()).toBe(200); + if (identity === 'admin') { + createdAdminId = (await created.json()).id; + expect(createdAdminId).toEqual(expect.any(String)); + } let jobId = 2147483647; let cvId = 2147483647; if (identity !== 'anonymous') { @@ -163,8 +171,12 @@ for (const identity of ['anonymous', 'normal', 'admin'] as const) { await expect(page.getByTestId('admin-version-badge')).toHaveCount(identity === 'admin' ? 1 : 0); } } finally { - await info.attach(`route-census-${identity}`, { body: JSON.stringify(evidence, null, 2), contentType: 'application/json' }); - await admin.dispose(); + try { + if (createdAdminId) expect((await admin.put(`${apiUrl}/users/${createdAdminId}/roles`, { headers: adminHeaders, data: { roles: [] } })).status()).toBe(204); + } finally { + await info.attach(`route-census-${identity}`, { body: JSON.stringify(evidence, null, 2), contentType: 'application/json' }); + await admin.dispose(); + } } }); } diff --git a/job-tracker-ui/e2e/phase7-admin-audit.spec.ts b/job-tracker-ui/e2e/phase7-admin-audit.spec.ts index 6820810..ad59919 100644 --- a/job-tracker-ui/e2e/phase7-admin-audit.spec.ts +++ b/job-tracker-ui/e2e/phase7-admin-audit.spec.ts @@ -21,7 +21,9 @@ async function login(records: RequestRecord[], api: APIRequestContext, email: st expect((await call(records, api, 'POST', '/auth/login', undefined, { email, password })).status()).toBe(200); const me = await json<{ id: string; sessionContext: string }>(await call(records, api, 'GET', '/auth/me')); const token = (await api.storageState()).cookies.find(value => value.name === 'XSRF-TOKEN'); expect(token).toBeDefined(); - return { id: me.id, email, headers: { 'X-CSRF-TOKEN': token!.value, 'X-Session-Context': me.sessionContext } }; + const headers = { 'X-CSRF-TOKEN': token!.value, 'X-Session-Context': me.sessionContext }; + if (email === 'e2e@example.test') expect((await call(records, api, 'PUT', '/auth/preferences/language', headers, { language: 'en' })).status()).toBe(204); + return { id: me.id, email, headers }; } async function createOwner(records: RequestRecord[], admin: APIRequestContext, headers: Headers) { const email = `p710-audit-${Date.now()}-${Math.random().toString(36).slice(2)}@example.test`; diff --git a/job-tracker-ui/e2e/phase7-admin-system.spec.ts b/job-tracker-ui/e2e/phase7-admin-system.spec.ts index 00eea13..9a30f8d 100644 --- a/job-tracker-ui/e2e/phase7-admin-system.spec.ts +++ b/job-tracker-ui/e2e/phase7-admin-system.spec.ts @@ -21,7 +21,9 @@ async function login(records: RequestRecord[], api: APIRequestContext, email: st expect((await call(records, api, 'POST', '/auth/login', undefined, { email, password })).status()).toBe(200); const me = await json<{ id: string; sessionContext: string }>(await call(records, api, 'GET', '/auth/me')); const token = (await api.storageState()).cookies.find(value => value.name === 'XSRF-TOKEN'); expect(token).toBeDefined(); - return { id: me.id, email, headers: { 'X-CSRF-TOKEN': token!.value, 'X-Session-Context': me.sessionContext } }; + const headers = { 'X-CSRF-TOKEN': token!.value, 'X-Session-Context': me.sessionContext }; + if (email === 'e2e@example.test') expect((await call(records, api, 'PUT', '/auth/preferences/language', headers, { language: 'en' })).status()).toBe(204); + return { id: me.id, email, headers }; } async function createOwner(records: RequestRecord[], admin: APIRequestContext, headers: Headers) { const email = `p710-system-${Date.now()}-${Math.random().toString(36).slice(2)}@example.test`; diff --git a/job-tracker-ui/e2e/phase7-admin-users.spec.ts b/job-tracker-ui/e2e/phase7-admin-users.spec.ts index a1814f4..ed4bef8 100644 --- a/job-tracker-ui/e2e/phase7-admin-users.spec.ts +++ b/job-tracker-ui/e2e/phase7-admin-users.spec.ts @@ -21,7 +21,9 @@ async function login(records: RequestRecord[], api: APIRequestContext, email: st expect((await call(records, api, 'POST', '/auth/login', undefined, { email, password })).status()).toBe(200); const me = await json<{ id: string; sessionContext: string }>(await call(records, api, 'GET', '/auth/me')); const token = (await api.storageState()).cookies.find(value => value.name === 'XSRF-TOKEN'); expect(token).toBeDefined(); - return { id: me.id, email, headers: { 'X-CSRF-TOKEN': token!.value, 'X-Session-Context': me.sessionContext } }; + const headers = { 'X-CSRF-TOKEN': token!.value, 'X-Session-Context': me.sessionContext }; + if (email === 'e2e@example.test') expect((await call(records, api, 'PUT', '/auth/preferences/language', headers, { language: 'en' })).status()).toBe(204); + return { id: me.id, email, headers }; } async function createOwner(records: RequestRecord[], admin: APIRequestContext, headers: Headers) { const email = `p710-admin-${Date.now()}-${Math.random().toString(36).slice(2)}@example.test`; @@ -68,6 +70,9 @@ function response(page: Page, method: string, path: string) { return page.waitFo test('[P7-10 Users A01 E09] real admin creation/role persistence revoke sessions while final-admin and disabled deletion remain protected', async ({ page, playwright, session }) => { const administrator=await login(session.requests, page.request, 'e2e@example.test'); + const administrators=(await json>(await call(session.requests, page.request, 'GET', '/users'))).filter(value => value.roles.includes('Admin')); + // A contaminated fixture must fail before a successful self-demotion can break later suites. + expect(administrators.map(value => value.id)).toEqual([administrator.id]); expect((await call(session.requests, page.request, 'PUT', `/users/${administrator.id}/roles`, administrator.headers, { roles: [] })).status()).toBe(409); await start(page, '/admin/users', 390); const email=`p710-created-${Date.now()}@example.test`; @@ -90,7 +95,11 @@ test('[P7-10 Users A01 E09] real admin creation/role persistence revoke sessions expect((await call(session.requests, other, 'GET', '/auth/me')).status()).toBe(401); expect((await page.request.delete(`${apiUrl}/users/${created.id}`, { headers: { ...administrator.headers, 'X-Confirm-Account-Deletion': email } })).status()).toBe(503); expect((await json>(await call(session.requests, page.request, 'GET', '/users'))).some(value => value.id===created.id)).toBe(true); - } finally { await other.dispose(); } + } finally { + try { + expect((await call(session.requests, session.admin, 'PUT', `/users/${created.id}/roles`, session.adminHeaders, { roles: [] })).status()).toBe(204); + } finally { await other.dispose(); } + } }); test('[P7-10 Users A01 E08] unavailable user list recovers by GET without fabricating empty state', async ({ page, session }, info) => { diff --git a/job-tracker-ui/playwright.config.ts b/job-tracker-ui/playwright.config.ts index 58bbed3..91cd7b9 100644 --- a/job-tracker-ui/playwright.config.ts +++ b/job-tracker-ui/playwright.config.ts @@ -35,8 +35,10 @@ export default defineConfig({ url: `${apiUrl}/health`, timeout: 120_000, reuseExistingServer: reuseServers, + stdout: "pipe", env: { ASPNETCORE_ENVIRONMENT: "Development", + Logging__LogLevel__Default: "Warning", Auth__Require: "true", Auth__JwtKey: "e2e-only-key-at-least-thirty-two-characters-long", Auth__AdminEmail: "e2e@example.test",