mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-26 21:47:20 +00:00
Record final Patrol reversal browser checks
Bind the six changed runtime files to completed production-component desktop and narrow checks, including scoped reversal, uncertainty and long-reason scrolling. Retain synthetic and unsupported-input limits without claiming installed acceptance. Change-source: pulse-maintainer
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
// Final affected confirmation content: real phone reversal, desktop viewport,
|
||||
// and long-reason pointer/keyboard review. No parent or stopped test replay.
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const {
|
||||
journey,
|
||||
report,
|
||||
output,
|
||||
} = require('/workspace/frontend-modern/browser-tests/patrol-rule-removal.cjs');
|
||||
(async () => {
|
||||
try {
|
||||
await journey('/workspace/frontend-modern', 'webkit', 320);
|
||||
await journey('/workspace/frontend-modern', 'chromium', 1365, false, true, true);
|
||||
await journey('/workspace/frontend-modern', 'webkit', 320, false, true, 'long');
|
||||
report.result = 'passed';
|
||||
} catch (error) {
|
||||
report.result = 'failed';
|
||||
report.failure = error.message;
|
||||
throw error;
|
||||
} finally {
|
||||
report.cleanup = {
|
||||
all_cases_closed: report.cases.every(
|
||||
(c) => c.cleanup.browser_closed && c.cleanup.server_closed,
|
||||
),
|
||||
};
|
||||
fs.writeFileSync(path.join(output, 'final-result.json'), JSON.stringify(report, null, 2));
|
||||
}
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
result: report.result,
|
||||
cases: report.cases.length,
|
||||
captures: report.captures.length,
|
||||
cleanup: report.cleanup,
|
||||
}),
|
||||
);
|
||||
})().catch((error) => {
|
||||
console.error(error.stack);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
// Complete the previously unmeasured scroll controls only. Mobile WebKit admits
|
||||
// keyboard scrolling, not mouse wheel; desktop Chromium admits pointer wheel.
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const {
|
||||
journey,
|
||||
report,
|
||||
output,
|
||||
} = require('/workspace/frontend-modern/browser-tests/patrol-rule-removal.cjs');
|
||||
(async () => {
|
||||
try {
|
||||
await journey('/workspace/frontend-modern', 'webkit', 320, false, true, 'long');
|
||||
await journey('/workspace/frontend-modern', 'chromium', 1365, false, true, 'long');
|
||||
report.result = 'passed';
|
||||
} catch (error) {
|
||||
report.result = 'failed';
|
||||
report.failure = error.message;
|
||||
throw error;
|
||||
} finally {
|
||||
report.cleanup = {
|
||||
all_cases_closed: report.cases.every(
|
||||
(c) => c.cleanup.browser_closed && c.cleanup.server_closed,
|
||||
),
|
||||
};
|
||||
fs.writeFileSync(path.join(output, 'long-final-result.json'), JSON.stringify(report, null, 2));
|
||||
}
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
result: report.result,
|
||||
cases: report.cases.length,
|
||||
captures: report.captures.length,
|
||||
cleanup: report.cleanup,
|
||||
}),
|
||||
);
|
||||
})().catch((error) => {
|
||||
console.error(error.stack);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -326,7 +326,10 @@ async function journey(root, engine, width, parent = false, resume = false, visu
|
||||
return route.fulfill({ json });
|
||||
});
|
||||
const capture = async (name) => {
|
||||
const file = path.join(output, `${parent ? 'parent' : engine}-${width}-${name}.png`);
|
||||
const file = path.join(
|
||||
output,
|
||||
`${parent ? 'parent' : engine}-${width}-${visualOnly === 'long' ? 'long-' : ''}${name}.png`,
|
||||
);
|
||||
await page.screenshot({
|
||||
path: file,
|
||||
fullPage: !visualOnly,
|
||||
@@ -403,6 +406,65 @@ async function journey(root, engine, width, parent = false, resume = false, visu
|
||||
actions.y + actions.height <= 901,
|
||||
'Confirmation action must not be clipped: ' + JSON.stringify({ box, actions }),
|
||||
);
|
||||
if (visualOnly === 'long') {
|
||||
const region = dialog.getByRole('region', { name: 'Rule scope and reason' });
|
||||
const initial = await region.evaluate((e) => ({
|
||||
top: e.scrollTop,
|
||||
height: e.clientHeight,
|
||||
scrollHeight: e.scrollHeight,
|
||||
}));
|
||||
assert.equal(initial.top, 0, 'Safe autofocus must not scroll past the rule scope');
|
||||
assert(
|
||||
initial.scrollHeight > initial.height,
|
||||
'Long reason needs a real scrolling region',
|
||||
);
|
||||
for (const text of ['Keep VM', 'fixture-keep', 'capacity', expected]) {
|
||||
const scopeBox = await region.getByText(text, { exact: true }).boundingBox();
|
||||
const regionBox = await region.boundingBox();
|
||||
assert(
|
||||
scopeBox &&
|
||||
regionBox &&
|
||||
scopeBox.y >= regionBox.y &&
|
||||
scopeBox.y + scopeBox.height <= regionBox.y + regionBox.height + 1,
|
||||
'The scope and exact ID are initially in view: ' + text,
|
||||
);
|
||||
}
|
||||
const input =
|
||||
engine === 'webkit' && width === 320 ? 'keyboard PageDown' : 'pointer wheel';
|
||||
if (input === 'keyboard PageDown') {
|
||||
// Playwright mobile WebKit does not implement mouse wheel. Use
|
||||
// its supported native keyboard scroll on the focusable region.
|
||||
await region.focus();
|
||||
await page.keyboard.press('PageDown');
|
||||
} else {
|
||||
await region.hover();
|
||||
await page.mouse.wheel(0, 3000);
|
||||
}
|
||||
await page.waitForFunction(
|
||||
() => document.querySelector('[aria-label="Rule scope and reason"]').scrollTop > 0,
|
||||
);
|
||||
const scrolled = await region.evaluate((e) => e.scrollTop);
|
||||
await capture('long-reason-scrolled');
|
||||
const footerAfter = await dialog
|
||||
.getByRole('button', { name: 'Remove this rule' })
|
||||
.boundingBox();
|
||||
assert.deepEqual(footerAfter, actions, 'The action footer must not move with the reason');
|
||||
await region.focus();
|
||||
await page.keyboard.press('Home');
|
||||
await page.waitForFunction(
|
||||
() => document.querySelector('[aria-label="Rule scope and reason"]').scrollTop === 0,
|
||||
);
|
||||
result.longReason = {
|
||||
initial,
|
||||
scrollInput: input,
|
||||
scrollPosition: scrolled,
|
||||
keyboardHomeReturned: true,
|
||||
stableFooter: true,
|
||||
};
|
||||
result.checks.push(
|
||||
'Long reason supports the recorded scroll input and keyboard Home, with initial scope/ID visible and a stable safe footer.',
|
||||
);
|
||||
}
|
||||
result.dialogs.push({ name, ruleId: expected, box, viewport: { width, height: 900 } });
|
||||
await page.keyboard.press('Escape');
|
||||
await dialog.waitFor({ state: 'detached' });
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "e90b10f9f2a8b75249eb78327d302ae27c39b528",
|
||||
"verified_at": "2026-10-05T18:27:23Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/api/patrol.ts",
|
||||
"frontend-modern/src/components/AI/FindingsPanel.tsx",
|
||||
"frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx",
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx",
|
||||
"frontend-modern/src/features/patrol/PatrolSuppressionRules.tsx",
|
||||
"frontend-modern/src/utils/apiClient.ts"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/api/patrol.ts": "5c58ef600ede08b04ccb8acd70d14d70ade52cb048f25e1075df328fe2e8324f",
|
||||
"frontend-modern/src/components/AI/FindingsPanel.tsx": "ae16913c0761aee84ec808a4be9d2782576e7afea78f64ea0b134d5d9e43f375",
|
||||
"frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx": "fa21aa6871c0930e23531bfa27614644871a73a353fd1971aebca1f4dcd1bc0e",
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx": "f011f93aa9950cf10ce93317f5baf9e25161b39b03ec1a124da6fdbfc20cc249",
|
||||
"frontend-modern/src/features/patrol/PatrolSuppressionRules.tsx": "6efd4b42234cdc432507786bb3c014262dbc02be400b9029f9af1e5bac871f35",
|
||||
"frontend-modern/src/utils/apiClient.ts": "56dc80b15c0e7854ee16ece8b902c9de519e54f6110d55dc2c7e735d72214295"
|
||||
},
|
||||
"routes": [
|
||||
"/patrol",
|
||||
"/patrol/activity#patrol-suppression-rules"
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1365,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"width": 320,
|
||||
"height": 900
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"Production Patrol surface, clients and Router mounted with real CSS in the offline browser fixture; API responses and tenant/session inputs are synthetic. No installed or reporter deletion is claimed.",
|
||||
"Assigned parent 53ec5fae creates a permanent narrow rule but has no manual-rule reversal surface. Existing Inbox and Finding options creator links lead to Activity in the repaired source.",
|
||||
"Final-content WebKit 320: manual list, scoped confirmation, cancel, exact-ID removal/readback, preserved unrelated/finding-backed decisions, wildcard confirmation, uncertain response, explicit reload, revoked access and organisation invalidation.",
|
||||
"Final-content Chromium 1365 viewport: exact manual identity and all-resource/all-category confirmation are visible with initial Cancel focus. Desktop deletion and strict transport checks were completed before the scroll-only markup repair; their client/handler content is unchanged.",
|
||||
"Long multiline reason at 320 dark WebKit and 1365 light Chromium: heading and action footer stay visible, initial resource/category/rule ID remain in view, reason scrolls independently. WebKit native keyboard PageDown and Home; Chromium pointer wheel and keyboard Home.",
|
||||
"Full-page screenshots omitted fixed confirmation panels. Separately inspected viewport captures and bounding boxes establish visual readability. No horizontal overflow, page errors or off-origin calls; browser/server closed in all cases."
|
||||
],
|
||||
"interactions": [
|
||||
"Written reason required by the real Inbox Create rule form; narrow POST with synthetic CSRF and exact tenant, then link opens the collection even if Router state precedes pushState.",
|
||||
"Escape/Cancel performs no DELETE and returns focus. Confirm verifies the same manual row with a fresh GET, sends one exact-ID DELETE and requires readback absence; unrelated rules and historic dismissals remain.",
|
||||
"Finding options Create rule from this has a labelled reason field, Reopen distinction and the same Activity link without creating a second rule.",
|
||||
"Lost DELETE response never retries or reports success; explicit reload reconciles. 403 fresh read stops before DELETE; org switch invalidates confirmation and old list with no cross-tenant action.",
|
||||
"Measured final confirmation boxes within desktop and narrow viewports; finished finite entrance animations for viewport inspection. Long-reason scroll leaves the footer fixed, and keyboard Home restores the initial scope.",
|
||||
"Playwright 1.56.1 matches the integration lock; Chromium 141.0.7390.37 and WebKit 26.0. Mobile WebKit wheel is unsupported; its failed aggregate remains evidence, with completed cases retained and the outstanding scroll check completed using supported keyboard input, without replaying their mutations."
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user