mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-14 08:26:46 +02:00
Merge branch 'codex/boat-environment-core' into codex/boat-environment
* codex/boat-environment-core: test: synchronize signoff retries and touch geometry
This commit is contained in:
commit
06906ab358
2 files changed
+37
-3
No files matched your search
@@ -21,13 +21,23 @@ async function swipeToLastOption(page: Page, picker: Locator, list = picker.getB
|
||||
}).toBeLessThanOrEqual(page.viewportSize()!.height);
|
||||
expect((await picker.boundingBox())!.y).toBeGreaterThanOrEqual(0);
|
||||
const session = await page.context().newCDPSession(page);
|
||||
const targetHandle = await last.elementHandle();
|
||||
expect(targetHandle).not.toBeNull();
|
||||
try {
|
||||
expect(await list.evaluate((element) => element.scrollHeight > element.clientHeight)).toBe(true);
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
const bounds = (await list.boundingBox())!;
|
||||
const target = (await last.boundingBox())!;
|
||||
// Momentum can finish between protocol round trips. Observe geometry and
|
||||
// scrollTop together so a stale target cannot trigger a swipe at the end.
|
||||
const { bounds, target, before } = await list.evaluate((element, option) => {
|
||||
const container = element.getBoundingClientRect();
|
||||
const item = option!.getBoundingClientRect();
|
||||
return {
|
||||
bounds: { x: container.x, y: container.y, width: container.width, height: container.height },
|
||||
target: { y: item.y, height: item.height },
|
||||
before: element.scrollTop,
|
||||
};
|
||||
}, targetHandle);
|
||||
if (target.y >= bounds.y && target.y + target.height <= bounds.y + bounds.height) break;
|
||||
const before = await list.evaluate((element) => element.scrollTop);
|
||||
const x = bounds.x + bounds.width / 2;
|
||||
const y = bounds.y + bounds.height - 20;
|
||||
const distance = Math.min(180, bounds.height - 40);
|
||||
@@ -49,6 +59,7 @@ async function swipeToLastOption(page: Page, picker: Locator, list = picker.getB
|
||||
expect(target.y).toBeGreaterThanOrEqual(bounds.y);
|
||||
expect(target.y + target.height).toBeLessThanOrEqual(bounds.y + bounds.height + 1);
|
||||
} finally {
|
||||
await targetHandle?.dispose();
|
||||
await session.detach();
|
||||
}
|
||||
return last;
|
||||
@@ -56,6 +67,25 @@ async function swipeToLastOption(page: Page, picker: Locator, list = picker.getB
|
||||
|
||||
test.use({ viewport: { width: 390, height: 844 }, hasTouch: true, isMobile: true });
|
||||
|
||||
test("touch scroll observation stays valid when momentum finishes between browser reads", async ({ page, browserName }) => {
|
||||
test.skip(browserName !== "chromium", "Native touch drags use Chromium's input protocol.");
|
||||
await page.setContent(`<div role="dialog" style="position:absolute;top:20px;left:20px;width:300px">
|
||||
<div role="listbox" style="height:150px;overflow-y:auto;touch-action:pan-y">
|
||||
${Array.from({ length: 12 }, (_, index) => `<div role="option" style="height:40px">Option ${index}</div>`).join("")}
|
||||
</div></div>`);
|
||||
const picker = page.getByRole("dialog"), list = picker.getByRole("listbox"), last = list.getByRole("option").last();
|
||||
const boundingBox = last.boundingBox.bind(last);
|
||||
// Reproduce the observed compositor interleaving: a returned target rectangle
|
||||
// is stale by the next scrollTop read because inertia reached the list end.
|
||||
last.boundingBox = async (options) => {
|
||||
const beforeMomentum = await boundingBox(options);
|
||||
await list.evaluate((element) => { element.scrollTop = element.scrollHeight - element.clientHeight; });
|
||||
return beforeMomentum;
|
||||
};
|
||||
await swipeToLastOption(page, picker, list, last);
|
||||
await expect(picker).toBeVisible();
|
||||
});
|
||||
|
||||
test("new-task assignee, model, and project sheets scroll by touch and retain the selected values", async ({ page, request, browserName }, testInfo) => {
|
||||
test.skip(browserName !== "chromium", "Native touch drags use Chromium's input protocol.");
|
||||
const company = await json(await request.post("/api/companies", {
|
||||
|
||||
@@ -309,6 +309,9 @@ async function retryAgentPatchWithCurrentLockOnConflict(
|
||||
if (issueRunLock.assigneeAgentId !== agent.agentId) return res;
|
||||
|
||||
const lockedRunId = issueRunLock.checkoutRunId ?? issueRunLock.executionRunId ?? fallbackRunId;
|
||||
// A new lock can belong to a newly promoted fixture run, not the completed
|
||||
// run we first invoked. Let that exact verified run settle before PATCH.
|
||||
await waitForFixtureRun(board, agent.agentId, issueId, lockedRunId);
|
||||
res = await agent.request.patch(`${BASE_URL}/api/issues/${issueId}`, {
|
||||
headers: { "X-Paperclip-Run-Id": lockedRunId },
|
||||
data: patchData,
|
||||
@@ -365,6 +368,7 @@ async function agentPatch(
|
||||
// run-lock race — leave it for the caller to assert on.
|
||||
if (issueRunLock.assigneeAgentId !== agent.agentId) break;
|
||||
const retryRunId = issueRunLock.checkoutRunId ?? issueRunLock.executionRunId ?? runId;
|
||||
await waitForFixtureRun(board, agent.agentId, issueId, retryRunId);
|
||||
res = await patchWith(retryRunId);
|
||||
}
|
||||
return res;
|
||||
|
||||
Reference in new issue
Block a user