From 4b432ea73a1006f762efc0796258780b35b763f6 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 23 May 2026 11:00:50 +0200 Subject: [PATCH] Delegate OpenTelemetryTab.targetInfo to the wrapped tab targetInfo was declared as `override var targetInfo = tab.targetInfo`, a stored property initialized once at construction. A traced tab therefore reported stale target info after navigation (the wrapped tab's targetInfo updates via events / updateTarget were never reflected), and writes to the wrapper never reached the underlying tab. Every targetId/url/type read on a traced tab (activate, getWindow, saveScreenshot filename, getAllUrls, screenshotB64's wait/updateTarget) could be wrong. Delegate get/set onto the wrapped tab, matching the existing lastMouseX/lastMouseY pattern. Verified red->green with OpenTelemetryTabTargetInfoTest (mockk Tab with a mutable backing targetInfo): the getter test mutates the underlying value after wrapping and asserts the wrapper reflects it; the setter test asserts a write propagates to the wrapped tab. Both fail on the snapshot impl, pass after the fix. Full :opentelemetry:jvmTest passes. --- .../kdriver/opentelemetry/OpenTelemetryTab.kt | 6 +- .../OpenTelemetryTabTargetInfoTest.kt | 76 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 opentelemetry/src/jvmTest/kotlin/dev/kdriver/opentelemetry/OpenTelemetryTabTargetInfoTest.kt diff --git a/opentelemetry/src/commonMain/kotlin/dev/kdriver/opentelemetry/OpenTelemetryTab.kt b/opentelemetry/src/commonMain/kotlin/dev/kdriver/opentelemetry/OpenTelemetryTab.kt index c9aff7512..26ee93402 100644 --- a/opentelemetry/src/commonMain/kotlin/dev/kdriver/opentelemetry/OpenTelemetryTab.kt +++ b/opentelemetry/src/commonMain/kotlin/dev/kdriver/opentelemetry/OpenTelemetryTab.kt @@ -413,7 +413,11 @@ class OpenTelemetryTab( override suspend fun sleep(t: Long) = tab.sleep(t) - override var targetInfo: Target.TargetInfo? = tab.targetInfo + override var targetInfo: Target.TargetInfo? + get() = tab.targetInfo + set(value) { + tab.targetInfo = value + } @InternalCdpApi override val events: Flow = tab.events diff --git a/opentelemetry/src/jvmTest/kotlin/dev/kdriver/opentelemetry/OpenTelemetryTabTargetInfoTest.kt b/opentelemetry/src/jvmTest/kotlin/dev/kdriver/opentelemetry/OpenTelemetryTabTargetInfoTest.kt new file mode 100644 index 000000000..eb9d071f2 --- /dev/null +++ b/opentelemetry/src/jvmTest/kotlin/dev/kdriver/opentelemetry/OpenTelemetryTabTargetInfoTest.kt @@ -0,0 +1,76 @@ +package dev.kdriver.opentelemetry + +import dev.kdriver.cdp.domain.Target +import dev.kdriver.core.tab.Tab +import io.mockk.every +import io.mockk.mockk +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.extension.RegisterExtension +import kotlin.test.Test +import kotlin.test.assertEquals + +/** + * Guards that [OpenTelemetryTab.targetInfo] delegates to the wrapped tab rather than capturing a + * one-time snapshot at construction (audit ISSUE-12). + * + * The buggy implementation declared `override var targetInfo = tab.targetInfo`, a stored property + * initialized once. As a result a traced tab kept reporting stale target info after navigation, and + * writes to it never reached the underlying tab — unlike `lastMouseX`/`lastMouseY`, which delegate. + */ +class OpenTelemetryTabTargetInfoTest { + + companion object { + @JvmField + @RegisterExtension + val otelTesting = OpenTelemetryExtension.create() + } + + private fun info(url: String) = Target.TargetInfo( + targetId = "t", + type = "page", + title = "", + url = url, + attached = true, + canAccessOpener = false, + ) + + @Test + fun `targetInfo getter reflects updates to the wrapped tab`() = runTest { + val tracer = otelTesting.openTelemetry.getTracer("test") + var backing: Target.TargetInfo? = info("about:blank") + val mockTab = mockk(relaxed = true) + every { mockTab.targetInfo } answers { backing } + + // Construction must not freeze a snapshot. + val traced = mockTab.withTracing(tracer) + + // The underlying tab navigates after the wrapper was created. + backing = info("https://example.com") + + assertEquals( + "https://example.com", + traced.targetInfo?.url, + "targetInfo should reflect the current value of the wrapped tab, not a snapshot", + ) + } + + @Test + fun `targetInfo setter propagates to the wrapped tab`() = runTest { + val tracer = otelTesting.openTelemetry.getTracer("test") + var backing: Target.TargetInfo? = info("about:blank") + val mockTab = mockk(relaxed = true) + every { mockTab.targetInfo } answers { backing } + every { mockTab.targetInfo = any() } answers { backing = firstArg() } + + val traced = mockTab.withTracing(tracer) + + traced.targetInfo = info("https://example.com") + + assertEquals( + "https://example.com", + backing?.url, + "Writing targetInfo on the wrapper should propagate to the wrapped tab", + ) + } +}