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(
|
function createStreamRenderStrategy(
|
||||||
config: StreamRenderStrategyConfig
|
config: StreamRenderStrategyConfig
|
||||||
): StreamRenderStrategy {
|
): StreamRenderStrategy {
|
||||||
@@ -376,12 +402,28 @@ function createForwardStreamStrategy(): StreamRenderStrategy {
|
|||||||
0,
|
0,
|
||||||
metrics.contentHeight - metrics.viewportHeight
|
metrics.contentHeight - metrics.viewportHeight
|
||||||
);
|
);
|
||||||
|
logWebBottomAnchorTransport("forward_scroll_to_bottom_start", {
|
||||||
|
animated,
|
||||||
|
bottomOffset,
|
||||||
|
metrics,
|
||||||
|
usedFlatList: Boolean(refs.flatListRef.current),
|
||||||
|
...getResolvedWebScrollContainerMetrics(refs),
|
||||||
|
});
|
||||||
if (refs.flatListRef.current) {
|
if (refs.flatListRef.current) {
|
||||||
refs.flatListRef.current.scrollToEnd?.({ animated });
|
refs.flatListRef.current.scrollToEnd?.({ animated });
|
||||||
refs.flatListRef.current.scrollToOffset?.({
|
refs.flatListRef.current.scrollToOffset?.({
|
||||||
offset: bottomOffset,
|
offset: bottomOffset,
|
||||||
animated,
|
animated,
|
||||||
});
|
});
|
||||||
|
if (typeof requestAnimationFrame === "function") {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
logWebBottomAnchorTransport("forward_scroll_to_bottom_after_flatlist", {
|
||||||
|
animated,
|
||||||
|
bottomOffset,
|
||||||
|
...getResolvedWebScrollContainerMetrics(refs),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const usedAnchor = scrollAnchorIntoView({ refs, animated });
|
const usedAnchor = scrollAnchorIntoView({ refs, animated });
|
||||||
@@ -394,6 +436,16 @@ function createForwardStreamStrategy(): StreamRenderStrategy {
|
|||||||
animated,
|
animated,
|
||||||
});
|
});
|
||||||
forceScrollContainerToBottom(refs, bottomOffset);
|
forceScrollContainerToBottom(refs, bottomOffset);
|
||||||
|
if (typeof requestAnimationFrame === "function") {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
logWebBottomAnchorTransport("forward_scroll_to_bottom_after_scrollview", {
|
||||||
|
animated,
|
||||||
|
bottomOffset,
|
||||||
|
usedAnchor,
|
||||||
|
...getResolvedWebScrollContainerMetrics(refs),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
scrollToOffset: ({ refs, offset, animated }) => {
|
scrollToOffset: ({ refs, offset, animated }) => {
|
||||||
if (refs.flatListRef.current) {
|
if (refs.flatListRef.current) {
|
||||||
|
|||||||
@@ -233,6 +233,20 @@ function deriveRetryDisposition(input: {
|
|||||||
: "retry-scroll";
|
: "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(
|
function createBottomAnchorControllerDriver(
|
||||||
input: CreateBottomAnchorControllerDriverInput
|
input: CreateBottomAnchorControllerDriverInput
|
||||||
): BottomAnchorControllerDriver {
|
): BottomAnchorControllerDriver {
|
||||||
@@ -344,18 +358,37 @@ function createBottomAnchorControllerDriver(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (verificationBlockedReason) {
|
if (verificationBlockedReason) {
|
||||||
|
input.log(
|
||||||
|
"attempt_verified",
|
||||||
|
getLogContext({
|
||||||
|
verificationPhase: "blocked",
|
||||||
|
verificationBlockedReason,
|
||||||
|
retries: attemptContext.retries,
|
||||||
|
measurementState: getDetailedMeasurementState(measurementState),
|
||||||
|
})
|
||||||
|
);
|
||||||
pendingVerification = attemptContext;
|
pendingVerification = attemptContext;
|
||||||
setBlockedReason(verificationBlockedReason);
|
setBlockedReason(verificationBlockedReason);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const verifiedNearBottom = input.isNearBottom();
|
const verifiedNearBottom = input.isNearBottom();
|
||||||
|
const retryDisposition = verifiedNearBottom
|
||||||
|
? null
|
||||||
|
: deriveRetryDisposition({
|
||||||
|
mode,
|
||||||
|
retries: attemptContext.retries,
|
||||||
|
verificationRetryMode:
|
||||||
|
input.getTransportBehavior().verificationRetryMode,
|
||||||
|
});
|
||||||
|
|
||||||
input.log(
|
input.log(
|
||||||
"attempt_verified",
|
"attempt_verified",
|
||||||
getLogContext({
|
getLogContext({
|
||||||
verifiedNearBottom,
|
verifiedNearBottom,
|
||||||
retries: attemptContext.retries,
|
retries: attemptContext.retries,
|
||||||
|
retryDisposition,
|
||||||
|
measurementState: getDetailedMeasurementState(measurementState),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -370,12 +403,6 @@ function createBottomAnchorControllerDriver(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const retryDisposition = deriveRetryDisposition({
|
|
||||||
mode,
|
|
||||||
retries: attemptContext.retries,
|
|
||||||
verificationRetryMode: input.getTransportBehavior().verificationRetryMode,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (retryDisposition === "retry-verify") {
|
if (retryDisposition === "retry-verify") {
|
||||||
pendingVerification = {
|
pendingVerification = {
|
||||||
requestId: attemptContext.requestId,
|
requestId: attemptContext.requestId,
|
||||||
@@ -397,7 +424,11 @@ function createBottomAnchorControllerDriver(
|
|||||||
|
|
||||||
input.log(
|
input.log(
|
||||||
"attempt_failed",
|
"attempt_failed",
|
||||||
getLogContext({ retries: attemptContext.retries })
|
getLogContext({
|
||||||
|
retries: attemptContext.retries,
|
||||||
|
retryDisposition,
|
||||||
|
measurementState: getDetailedMeasurementState(measurementState),
|
||||||
|
})
|
||||||
);
|
);
|
||||||
pendingVerification = null;
|
pendingVerification = null;
|
||||||
if (isRequestAttempt && currentRequest) {
|
if (isRequestAttempt && currentRequest) {
|
||||||
@@ -443,10 +474,11 @@ function createBottomAnchorControllerDriver(
|
|||||||
kind: "attempt",
|
kind: "attempt",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
attemptHandle = null;
|
attemptHandle = null;
|
||||||
|
const measurementState = input.getMeasurementState();
|
||||||
const nextBlockedReason = deriveBottomAnchorBlockedReason({
|
const nextBlockedReason = deriveBottomAnchorBlockedReason({
|
||||||
pendingRequest,
|
pendingRequest,
|
||||||
isAuthoritativeHistoryReady: input.getIsAuthoritativeHistoryReady(),
|
isAuthoritativeHistoryReady: input.getIsAuthoritativeHistoryReady(),
|
||||||
measurementState: input.getMeasurementState(),
|
measurementState,
|
||||||
pendingVerificationRequestId: pendingVerification?.requestId ?? null,
|
pendingVerificationRequestId: pendingVerification?.requestId ?? null,
|
||||||
});
|
});
|
||||||
setBlockedReason(nextBlockedReason);
|
setBlockedReason(nextBlockedReason);
|
||||||
@@ -462,6 +494,16 @@ function createBottomAnchorControllerDriver(
|
|||||||
!shouldAttemptForPendingRequest &&
|
!shouldAttemptForPendingRequest &&
|
||||||
!shouldAttemptForStickyVerification
|
!shouldAttemptForStickyVerification
|
||||||
) {
|
) {
|
||||||
|
input.log(
|
||||||
|
"attempt_started",
|
||||||
|
getLogContext({
|
||||||
|
attemptPhase: "skipped",
|
||||||
|
nextBlockedReason,
|
||||||
|
shouldAttemptForPendingRequest,
|
||||||
|
shouldAttemptForStickyVerification,
|
||||||
|
measurementState: getDetailedMeasurementState(measurementState),
|
||||||
|
})
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user