Add detailed logging to bottom anchor controller and scroll strategy
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
const baseURL =
|
||||
process.env.E2E_BASE_URL ??
|
||||
`http://localhost:${process.env.E2E_METRO_PORT ?? "8081"}`;
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./e2e",
|
||||
globalSetup: "./e2e/global-setup.ts",
|
||||
timeout: 60_000,
|
||||
expect: {
|
||||
timeout: 10_000,
|
||||
},
|
||||
fullyParallel: false,
|
||||
workers: 1,
|
||||
retries: 0,
|
||||
reporter: [["list"]],
|
||||
use: {
|
||||
baseURL,
|
||||
trace: "retain-on-failure",
|
||||
screenshot: "only-on-failure",
|
||||
video: "retain-on-failure",
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: "Desktop Safari",
|
||||
use: { ...devices["Desktop Safari"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -245,6 +245,32 @@ function forceScrollContainerToBottom(
|
||||
}
|
||||
}
|
||||
|
||||
function logWebBottomAnchorTransport(
|
||||
event: string,
|
||||
details: Record<string, unknown>
|
||||
): void {
|
||||
if (!(globalThis as { __DEV__?: boolean }).__DEV__) {
|
||||
return;
|
||||
}
|
||||
console.debug("[BottomAnchorTransport]", event, details);
|
||||
}
|
||||
|
||||
function getResolvedWebScrollContainerMetrics(
|
||||
refs: StreamRenderRefs
|
||||
): Record<string, unknown> {
|
||||
const scrollNode = resolveWebScrollContainerNode(refs);
|
||||
if (!scrollNode) {
|
||||
return { hasScrollNode: false };
|
||||
}
|
||||
return {
|
||||
hasScrollNode: true,
|
||||
scrollTop: scrollNode.scrollTop,
|
||||
scrollHeight: scrollNode.scrollHeight,
|
||||
clientHeight: scrollNode.clientHeight,
|
||||
tagName: scrollNode.tagName,
|
||||
};
|
||||
}
|
||||
|
||||
function createStreamRenderStrategy(
|
||||
config: StreamRenderStrategyConfig
|
||||
): StreamRenderStrategy {
|
||||
@@ -376,12 +402,28 @@ function createForwardStreamStrategy(): StreamRenderStrategy {
|
||||
0,
|
||||
metrics.contentHeight - metrics.viewportHeight
|
||||
);
|
||||
logWebBottomAnchorTransport("forward_scroll_to_bottom_start", {
|
||||
animated,
|
||||
bottomOffset,
|
||||
metrics,
|
||||
usedFlatList: Boolean(refs.flatListRef.current),
|
||||
...getResolvedWebScrollContainerMetrics(refs),
|
||||
});
|
||||
if (refs.flatListRef.current) {
|
||||
refs.flatListRef.current.scrollToEnd?.({ animated });
|
||||
refs.flatListRef.current.scrollToOffset?.({
|
||||
offset: bottomOffset,
|
||||
animated,
|
||||
});
|
||||
if (typeof requestAnimationFrame === "function") {
|
||||
requestAnimationFrame(() => {
|
||||
logWebBottomAnchorTransport("forward_scroll_to_bottom_after_flatlist", {
|
||||
animated,
|
||||
bottomOffset,
|
||||
...getResolvedWebScrollContainerMetrics(refs),
|
||||
});
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
const usedAnchor = scrollAnchorIntoView({ refs, animated });
|
||||
@@ -394,6 +436,16 @@ function createForwardStreamStrategy(): StreamRenderStrategy {
|
||||
animated,
|
||||
});
|
||||
forceScrollContainerToBottom(refs, bottomOffset);
|
||||
if (typeof requestAnimationFrame === "function") {
|
||||
requestAnimationFrame(() => {
|
||||
logWebBottomAnchorTransport("forward_scroll_to_bottom_after_scrollview", {
|
||||
animated,
|
||||
bottomOffset,
|
||||
usedAnchor,
|
||||
...getResolvedWebScrollContainerMetrics(refs),
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
scrollToOffset: ({ refs, offset, animated }) => {
|
||||
if (refs.flatListRef.current) {
|
||||
|
||||
@@ -233,6 +233,20 @@ function deriveRetryDisposition(input: {
|
||||
: "retry-scroll";
|
||||
}
|
||||
|
||||
function getDetailedMeasurementState(
|
||||
measurementState: ControllerMeasurementState
|
||||
): Record<string, unknown> {
|
||||
return {
|
||||
containerKey: measurementState.containerKey,
|
||||
viewportWidth: measurementState.viewportWidth,
|
||||
viewportHeight: measurementState.viewportHeight,
|
||||
contentHeight: measurementState.contentHeight,
|
||||
offsetY: measurementState.offsetY,
|
||||
viewportMeasuredForKey: measurementState.viewportMeasuredForKey,
|
||||
contentMeasuredForKey: measurementState.contentMeasuredForKey,
|
||||
};
|
||||
}
|
||||
|
||||
function createBottomAnchorControllerDriver(
|
||||
input: CreateBottomAnchorControllerDriverInput
|
||||
): BottomAnchorControllerDriver {
|
||||
@@ -344,18 +358,37 @@ function createBottomAnchorControllerDriver(
|
||||
});
|
||||
|
||||
if (verificationBlockedReason) {
|
||||
input.log(
|
||||
"attempt_verified",
|
||||
getLogContext({
|
||||
verificationPhase: "blocked",
|
||||
verificationBlockedReason,
|
||||
retries: attemptContext.retries,
|
||||
measurementState: getDetailedMeasurementState(measurementState),
|
||||
})
|
||||
);
|
||||
pendingVerification = attemptContext;
|
||||
setBlockedReason(verificationBlockedReason);
|
||||
return;
|
||||
}
|
||||
|
||||
const verifiedNearBottom = input.isNearBottom();
|
||||
const retryDisposition = verifiedNearBottom
|
||||
? null
|
||||
: deriveRetryDisposition({
|
||||
mode,
|
||||
retries: attemptContext.retries,
|
||||
verificationRetryMode:
|
||||
input.getTransportBehavior().verificationRetryMode,
|
||||
});
|
||||
|
||||
input.log(
|
||||
"attempt_verified",
|
||||
getLogContext({
|
||||
verifiedNearBottom,
|
||||
retries: attemptContext.retries,
|
||||
retryDisposition,
|
||||
measurementState: getDetailedMeasurementState(measurementState),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -370,12 +403,6 @@ function createBottomAnchorControllerDriver(
|
||||
return;
|
||||
}
|
||||
|
||||
const retryDisposition = deriveRetryDisposition({
|
||||
mode,
|
||||
retries: attemptContext.retries,
|
||||
verificationRetryMode: input.getTransportBehavior().verificationRetryMode,
|
||||
});
|
||||
|
||||
if (retryDisposition === "retry-verify") {
|
||||
pendingVerification = {
|
||||
requestId: attemptContext.requestId,
|
||||
@@ -397,7 +424,11 @@ function createBottomAnchorControllerDriver(
|
||||
|
||||
input.log(
|
||||
"attempt_failed",
|
||||
getLogContext({ retries: attemptContext.retries })
|
||||
getLogContext({
|
||||
retries: attemptContext.retries,
|
||||
retryDisposition,
|
||||
measurementState: getDetailedMeasurementState(measurementState),
|
||||
})
|
||||
);
|
||||
pendingVerification = null;
|
||||
if (isRequestAttempt && currentRequest) {
|
||||
@@ -443,10 +474,11 @@ function createBottomAnchorControllerDriver(
|
||||
kind: "attempt",
|
||||
callback: () => {
|
||||
attemptHandle = null;
|
||||
const measurementState = input.getMeasurementState();
|
||||
const nextBlockedReason = deriveBottomAnchorBlockedReason({
|
||||
pendingRequest,
|
||||
isAuthoritativeHistoryReady: input.getIsAuthoritativeHistoryReady(),
|
||||
measurementState: input.getMeasurementState(),
|
||||
measurementState,
|
||||
pendingVerificationRequestId: pendingVerification?.requestId ?? null,
|
||||
});
|
||||
setBlockedReason(nextBlockedReason);
|
||||
@@ -462,6 +494,16 @@ function createBottomAnchorControllerDriver(
|
||||
!shouldAttemptForPendingRequest &&
|
||||
!shouldAttemptForStickyVerification
|
||||
) {
|
||||
input.log(
|
||||
"attempt_started",
|
||||
getLogContext({
|
||||
attemptPhase: "skipped",
|
||||
nextBlockedReason,
|
||||
shouldAttemptForPendingRequest,
|
||||
shouldAttemptForStickyVerification,
|
||||
measurementState: getDetailedMeasurementState(measurementState),
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user