From 24fd064cb242e0e2c731abf5e2dcbaecc9f9ce43 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Thu, 19 Mar 2026 22:13:42 +0100 Subject: [PATCH 1/3] feat: allow null expect/fetch params --- README.md | 2 +- build.gradle.kts | 2 +- .../core/network/BaseBatchRequestExpectation.kt | 6 ++---- .../kdriver/core/network/BaseFetchInterception.kt | 8 +++----- .../kdriver/core/network/BaseRequestExpectation.kt | 7 +++---- .../kdriver/core/network/BatchRequestExpectation.kt | 4 +--- .../dev/kdriver/core/network/FetchInterception.kt | 8 +++----- .../dev/kdriver/core/network/RequestExpectation.kt | 4 +--- .../kotlin/dev/kdriver/core/tab/DefaultTab.kt | 10 +++++----- .../commonMain/kotlin/dev/kdriver/core/tab/Tab.kt | 12 +++++------- docs/home/quickstart.md | 2 +- 11 files changed, 26 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index d04418458..342a7db6a 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ To use kdriver, add the following to your `build.gradle.kts`: ```kotlin dependencies { - implementation("dev.kdriver:core:0.5.7") + implementation("dev.kdriver:core:0.5.8") } ``` diff --git a/build.gradle.kts b/build.gradle.kts index ec1bd8d66..8cd2248e0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { allprojects { group = "dev.kdriver" - version = "0.5.7" + version = "0.5.8" project.ext.set("url", "https://github.com/cdpdriver/kdriver") project.ext.set("license.name", "Apache 2.0") project.ext.set("license.url", "https://www.apache.org/licenses/LICENSE-2.0.txt") diff --git a/core/src/commonMain/kotlin/dev/kdriver/core/network/BaseBatchRequestExpectation.kt b/core/src/commonMain/kotlin/dev/kdriver/core/network/BaseBatchRequestExpectation.kt index 53e3bc1ad..dc3d41c7e 100644 --- a/core/src/commonMain/kotlin/dev/kdriver/core/network/BaseBatchRequestExpectation.kt +++ b/core/src/commonMain/kotlin/dev/kdriver/core/network/BaseBatchRequestExpectation.kt @@ -7,10 +7,9 @@ import dev.kdriver.core.tab.Tab */ class BaseBatchRequestExpectation( tab: Tab, - urlPatterns: List, + urlPatterns: List, ) : BatchRequestExpectation { - - override val expectations: Map = + override val expectations: Map = urlPatterns.associateWith { pattern -> BaseRequestExpectation(tab, pattern) } override suspend fun use(block: suspend BatchRequestExpectation.() -> T): T { @@ -20,5 +19,4 @@ class BaseBatchRequestExpectation( } return nest(0, block) } - } diff --git a/core/src/commonMain/kotlin/dev/kdriver/core/network/BaseFetchInterception.kt b/core/src/commonMain/kotlin/dev/kdriver/core/network/BaseFetchInterception.kt index 28674f27f..b4e27b996 100644 --- a/core/src/commonMain/kotlin/dev/kdriver/core/network/BaseFetchInterception.kt +++ b/core/src/commonMain/kotlin/dev/kdriver/core/network/BaseFetchInterception.kt @@ -16,11 +16,10 @@ import kotlin.coroutines.coroutineContext */ open class BaseFetchInterception( private val tab: Tab, - override val urlPattern: String, - override val requestStage: Fetch.RequestStage, - override val resourceType: Network.ResourceType, + override val urlPattern: String? = null, + override val requestStage: Fetch.RequestStage? = null, + override val resourceType: Network.ResourceType? = null, ) : FetchInterception { - private var responseDeferred = CompletableDeferred() private var job: Job? = null @@ -131,5 +130,4 @@ open class BaseFetchInterception( binaryResponseHeaders = binaryResponseHeaders ) } - } diff --git a/core/src/commonMain/kotlin/dev/kdriver/core/network/BaseRequestExpectation.kt b/core/src/commonMain/kotlin/dev/kdriver/core/network/BaseRequestExpectation.kt index 7b5fad057..5e9e1e9a9 100644 --- a/core/src/commonMain/kotlin/dev/kdriver/core/network/BaseRequestExpectation.kt +++ b/core/src/commonMain/kotlin/dev/kdriver/core/network/BaseRequestExpectation.kt @@ -14,9 +14,8 @@ import kotlin.coroutines.coroutineContext */ open class BaseRequestExpectation( private val tab: Tab, - override val urlPattern: Regex, + override val urlPattern: Regex? = null, ) : RequestExpectation { - private var requestJob: Job? = null private var responseJob: Job? = null private var loadingFinishedJob: Job? = null @@ -29,7 +28,8 @@ open class BaseRequestExpectation( private val requestHandler: suspend (Network.RequestWillBeSentParameter) -> Unit = requestHandler@{ event -> - if (!urlPattern.containsMatchIn(event.request.url)) return@requestHandler + val urlPattern = urlPattern + if (urlPattern != null && !urlPattern.containsMatchIn(event.request.url)) return@requestHandler requestId = event.requestId requestDeferred.complete(event) requestJob?.cancel() @@ -103,5 +103,4 @@ open class BaseRequestExpectation( loadingFinishedDeferred.await() // Ensure the loading is finished before fetching the body return EncodedBody(tab.network.getResponseBody(requestId)) } - } diff --git a/core/src/commonMain/kotlin/dev/kdriver/core/network/BatchRequestExpectation.kt b/core/src/commonMain/kotlin/dev/kdriver/core/network/BatchRequestExpectation.kt index 0fb7ffc48..cbb799f03 100644 --- a/core/src/commonMain/kotlin/dev/kdriver/core/network/BatchRequestExpectation.kt +++ b/core/src/commonMain/kotlin/dev/kdriver/core/network/BatchRequestExpectation.kt @@ -4,16 +4,14 @@ package dev.kdriver.core.network * Represents a batch of request expectations, each defined by a URL pattern and its corresponding expectation. */ interface BatchRequestExpectation { - /** * A map of URL patterns (as [Regex]) to their corresponding [RequestExpectation]s. */ - val expectations: Map + val expectations: Map /** * Activate all expectations for the duration of [block]. Expectations are enabled concurrently * by nesting RequestExpectation.use calls, so all handlers are active while [block] executes. */ suspend fun use(block: suspend BatchRequestExpectation.() -> T): T - } diff --git a/core/src/commonMain/kotlin/dev/kdriver/core/network/FetchInterception.kt b/core/src/commonMain/kotlin/dev/kdriver/core/network/FetchInterception.kt index e98f81991..a679214ff 100644 --- a/core/src/commonMain/kotlin/dev/kdriver/core/network/FetchInterception.kt +++ b/core/src/commonMain/kotlin/dev/kdriver/core/network/FetchInterception.kt @@ -14,21 +14,20 @@ import dev.kdriver.cdp.domain.Network * @param resourceType The type of resource to intercept (e.g., document, script, etc.). */ interface FetchInterception { - /** * The URL pattern to match requests and responses. */ - val urlPattern: String + val urlPattern: String? /** * The stage of the fetch request to intercept (e.g., request or response). */ - val requestStage: Fetch.RequestStage + val requestStage: Fetch.RequestStage? /** * The type of resource to intercept (e.g., document, script, etc.). */ - val resourceType: Network.ResourceType + val resourceType: Network.ResourceType? /** * Resets the internal state, allowing the interception to be reused. @@ -125,5 +124,4 @@ interface FetchInterception { responseHeaders: List? = null, binaryResponseHeaders: String? = null, ) - } diff --git a/core/src/commonMain/kotlin/dev/kdriver/core/network/RequestExpectation.kt b/core/src/commonMain/kotlin/dev/kdriver/core/network/RequestExpectation.kt index 736f8c825..e7f69c62d 100644 --- a/core/src/commonMain/kotlin/dev/kdriver/core/network/RequestExpectation.kt +++ b/core/src/commonMain/kotlin/dev/kdriver/core/network/RequestExpectation.kt @@ -11,11 +11,10 @@ import dev.kdriver.cdp.domain.Network * @param urlPattern The URL pattern to match requests and responses. */ interface RequestExpectation { - /** * The URL pattern to match requests and responses. */ - val urlPattern: Regex + val urlPattern: Regex? /** * Resets the internal state, allowing the expectation to be reused. @@ -51,5 +50,4 @@ interface RequestExpectation { * Fetches the raw response body once it has been received. */ suspend fun getRawResponseBody(): EncodedBody - } diff --git a/core/src/commonMain/kotlin/dev/kdriver/core/tab/DefaultTab.kt b/core/src/commonMain/kotlin/dev/kdriver/core/tab/DefaultTab.kt index 24349d481..786935cda 100644 --- a/core/src/commonMain/kotlin/dev/kdriver/core/tab/DefaultTab.kt +++ b/core/src/commonMain/kotlin/dev/kdriver/core/tab/DefaultTab.kt @@ -620,23 +620,23 @@ open class DefaultTab( } override suspend fun expect( - urlPattern: Regex, + urlPattern: Regex?, block: suspend RequestExpectation.() -> T, ): T { return BaseRequestExpectation(this, urlPattern).use(block) } override suspend fun expectBatch( - urlPatterns: List, + urlPatterns: List, block: suspend BatchRequestExpectation.() -> T, ): T { return BaseBatchRequestExpectation(this, urlPatterns).use(block) } override suspend fun intercept( - urlPattern: String, - requestStage: Fetch.RequestStage, - resourceType: Network.ResourceType, + urlPattern: String?, + requestStage: Fetch.RequestStage?, + resourceType: Network.ResourceType?, block: suspend FetchInterception.() -> T, ): T { return BaseFetchInterception(this, urlPattern, requestStage, resourceType).use(block) diff --git a/core/src/commonMain/kotlin/dev/kdriver/core/tab/Tab.kt b/core/src/commonMain/kotlin/dev/kdriver/core/tab/Tab.kt index 51a6f6931..99c97f7ae 100644 --- a/core/src/commonMain/kotlin/dev/kdriver/core/tab/Tab.kt +++ b/core/src/commonMain/kotlin/dev/kdriver/core/tab/Tab.kt @@ -32,7 +32,6 @@ import kotlin.io.encoding.ExperimentalEncodingApi * ``` */ interface Tab : Connection { - /** * Last mouse X position for this tab (for natural trajectories in anti-detection). * Each tab maintains its own mouse position to prevent concurrent operations from interfering. @@ -448,7 +447,7 @@ interface Tab : Connection { * @param block The block to execute during which the expectation is active. */ suspend fun expect( - urlPattern: Regex, + urlPattern: Regex? = null, block: suspend RequestExpectation.() -> T, ): T @@ -460,7 +459,7 @@ interface Tab : Connection { * @param block The block to execute during which the expectations are active. */ suspend fun expectBatch( - urlPatterns: List, + urlPatterns: List, block: suspend BatchRequestExpectation.() -> T, ): T @@ -479,9 +478,9 @@ interface Tab : Connection { * } */ suspend fun intercept( - urlPattern: String, - requestStage: Fetch.RequestStage, - resourceType: Network.ResourceType, + urlPattern: String? = null, + requestStage: Fetch.RequestStage? = null, + resourceType: Network.ResourceType? = null, block: suspend FetchInterception.() -> T, ): T @@ -528,5 +527,4 @@ interface Tab : Connection { * @return List of URLs as [String]. */ suspend fun getAllUrls(absolute: Boolean = true): List - } diff --git a/docs/home/quickstart.md b/docs/home/quickstart.md index 7198520be..932432d23 100644 --- a/docs/home/quickstart.md +++ b/docs/home/quickstart.md @@ -12,7 +12,7 @@ To install, add the dependency to your `build.gradle.kts`: ```kotlin dependencies { - implementation("dev.kdriver:core:0.5.7") + implementation("dev.kdriver:core:0.5.8") } ``` From 69a7acfa548a6725b8fe034852c5390287ce42c2 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Thu, 19 Mar 2026 22:23:18 +0100 Subject: [PATCH 2/3] fix telemetry build --- .../kdriver/opentelemetry/OpenTelemetryTab.kt | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/opentelemetry/src/commonMain/kotlin/dev/kdriver/opentelemetry/OpenTelemetryTab.kt b/opentelemetry/src/commonMain/kotlin/dev/kdriver/opentelemetry/OpenTelemetryTab.kt index f6b6e3ed4..c9aff7512 100644 --- a/opentelemetry/src/commonMain/kotlin/dev/kdriver/opentelemetry/OpenTelemetryTab.kt +++ b/opentelemetry/src/commonMain/kotlin/dev/kdriver/opentelemetry/OpenTelemetryTab.kt @@ -30,7 +30,6 @@ class OpenTelemetryTab( private val tab: Tab, private val tracer: Tracer, ) : Tab { - override var lastMouseX: Double? get() = tab.lastMouseX set(value) { @@ -339,30 +338,30 @@ class OpenTelemetryTab( } override suspend fun expect( - urlPattern: Regex, + urlPattern: Regex?, block: suspend RequestExpectation.() -> T, ): T = executeInSpan("kdriver.tab.expect", SpanKind.INTERNAL) { span -> - span.setAttribute("urlPattern", urlPattern.pattern) + urlPattern?.pattern?.let { span.setAttribute("urlPattern", it) } tab.expect(urlPattern, block) } override suspend fun expectBatch( - urlPatterns: List, + urlPatterns: List, block: suspend BatchRequestExpectation.() -> T, ): T = executeInSpan("kdriver.tab.expectBatch", SpanKind.INTERNAL) { span -> - span.setAttribute("urlPatterns", urlPatterns.joinToString(",") { it.pattern }) + span.setAttribute("urlPatterns", urlPatterns.joinToString(",") { it?.pattern ?: "null" }) tab.expectBatch(urlPatterns, block) } override suspend fun intercept( - urlPattern: String, - requestStage: Fetch.RequestStage, - resourceType: Network.ResourceType, + urlPattern: String?, + requestStage: Fetch.RequestStage?, + resourceType: Network.ResourceType?, block: suspend FetchInterception.() -> T, ): T = executeInSpan("kdriver.tab.intercept", SpanKind.INTERNAL) { span -> - span.setAttribute("urlPattern", urlPattern) - span.setAttribute("requestStage", requestStage.name) - span.setAttribute("resourceType", resourceType.name) + urlPattern?.let { span.setAttribute("urlPattern", it) } + requestStage?.name?.let { span.setAttribute("requestStage", it) } + resourceType?.name?.let { span.setAttribute("resourceType", it) } tab.intercept(urlPattern, requestStage, resourceType, block) } @@ -450,5 +449,4 @@ class OpenTelemetryTab( span.end() } } - } From 23fe89b5a7642a22f86a1fd99520779858fc11c5 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Thu, 19 Mar 2026 22:34:11 +0100 Subject: [PATCH 3/3] add tests for non filtered cases --- .../core/network/FetchInterceptionTest.kt | 19 ++++++++++++++++++ .../core/network/RequestExpectationTest.kt | 20 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/core/src/jvmTest/kotlin/dev/kdriver/core/network/FetchInterceptionTest.kt b/core/src/jvmTest/kotlin/dev/kdriver/core/network/FetchInterceptionTest.kt index 56c620dbd..84b34284e 100644 --- a/core/src/jvmTest/kotlin/dev/kdriver/core/network/FetchInterceptionTest.kt +++ b/core/src/jvmTest/kotlin/dev/kdriver/core/network/FetchInterceptionTest.kt @@ -32,6 +32,25 @@ class FetchInterceptionTest { browser.stop() } + @Test + fun testInterceptNoResourceTypeFilter() = runBlocking { + val browser = createBrowser(this, headless = true, sandbox = false) + val tab = browser.mainTab ?: error("Main tab is not available") + + val userData = tab.intercept( + "*/user-data.json", + Fetch.RequestStage.RESPONSE, + ) { + tab.get(sampleFile("profile.html")) + val originalResponse = withTimeout(3000L) { getResponseBody() } + withTimeout(3000L) { continueRequest() } + originalResponse + } + + assertEquals("Zendriver", userData.name) + browser.stop() + } + @Test fun testInterceptWithReload() = runBlocking { val browser = createBrowser(this, headless = true, sandbox = false) diff --git a/core/src/jvmTest/kotlin/dev/kdriver/core/network/RequestExpectationTest.kt b/core/src/jvmTest/kotlin/dev/kdriver/core/network/RequestExpectationTest.kt index e1ed712b9..19df15b97 100644 --- a/core/src/jvmTest/kotlin/dev/kdriver/core/network/RequestExpectationTest.kt +++ b/core/src/jvmTest/kotlin/dev/kdriver/core/network/RequestExpectationTest.kt @@ -32,6 +32,26 @@ class RequestExpectationTest { browser.stop() } + @Test + fun testExpectNoFilter() = runBlocking { + val browser = createBrowser(this, headless = true, sandbox = false) + val tab = browser.mainTab ?: error("Main tab is not available") + + tab.expect { + tab.get(sampleFile("groceries.html")) + + val request = withTimeout(3000L) { this@expect.getRequest() } + val response = withTimeout(3000L) { this@expect.getResponse() } + val responseBody = withTimeout(3000L) { this@expect.getRawResponseBody() } + + assertEquals(request.url, response.url) + assertEquals(200, response.status) + assertTrue(responseBody.body.isNotEmpty()) + } + + browser.stop() + } + @Test fun testExpectWithReload() = runBlocking { val browser = createBrowser(this, headless = true, sandbox = false)