From 2194ce83671e435bbc4cf849acbdfd1a751d2c33 Mon Sep 17 00:00:00 2001 From: Joffrey Bion Date: Sat, 8 Feb 2025 02:32:39 +0100 Subject: [PATCH] Add workaround for optional Cookie.expired field See https://github.com/ChromeDevTools/devtools-protocol/issues/317 Resolves: https://github.com/joffrey-bion/chrome-devtools-kotlin/issues/494 --- .../devtools/protocol/generator/Generator.kt | 9 ++-- .../{json => preprocessing}/EnumFixer.kt | 3 +- .../preprocessing/JsonPreProcessor.kt | 43 +++++++++++++++++++ .../extensions/CrossDomainExtensions.kt | 4 +- src/jvmTest/kotlin/IntegrationTestBase.kt | 23 ++++++++++ src/jvmTest/kotlin/ZenikaIntegrationTests.kt | 5 +++ 6 files changed, 80 insertions(+), 7 deletions(-) rename protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/{json => preprocessing}/EnumFixer.kt (98%) create mode 100644 protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/preprocessing/JsonPreProcessor.kt diff --git a/protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/generator/Generator.kt b/protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/generator/Generator.kt index e05c8bee..6e5a2c28 100644 --- a/protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/generator/Generator.kt +++ b/protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/generator/Generator.kt @@ -3,12 +3,11 @@ package org.hildan.chrome.devtools.protocol.generator import com.squareup.kotlinpoet.FileSpec import org.hildan.chrome.devtools.protocol.json.ChromeProtocolDescriptor import org.hildan.chrome.devtools.protocol.json.TargetType -import org.hildan.chrome.devtools.protocol.json.pullNestedEnumsToTopLevel import org.hildan.chrome.devtools.protocol.model.ChromeDPDomain import org.hildan.chrome.devtools.protocol.model.toChromeDPDomain import org.hildan.chrome.devtools.protocol.names.Annotations import org.hildan.chrome.devtools.protocol.names.ExtDeclarations -import java.nio.file.Files +import org.hildan.chrome.devtools.protocol.preprocessing.preprocessed import java.nio.file.Path import kotlin.io.path.* @@ -31,7 +30,7 @@ class Generator( generatedSourcesDir.deleteRecursively() generatedSourcesDir.createDirectories() - val domains = loadProtocolDomains() + val domains = readProtocolDomains() domains.forEach(::generateDomainFiles) val domainsByName = domains.associateBy { it.names.domainName } @@ -52,12 +51,12 @@ class Generator( generateChildSessionsFiles(childTargets = targets.filterNot { it.kotlinName == "Browser" }) } - private fun loadProtocolDomains(): List { + private fun readProtocolDomains(): List { val descriptors = protocolFiles.map { ChromeProtocolDescriptor.fromJson(it.readText()) } if (descriptors.distinctBy { it.version }.size > 1) { error("Some descriptors have differing versions: ${descriptors.map { it.version }}") } - return descriptors.flatMap { it.domains }.map { it.pullNestedEnumsToTopLevel().toChromeDPDomain() } + return descriptors.flatMap { it.domains }.preprocessed().map { it.toChromeDPDomain() } } private fun generateDomainFiles(domain: ChromeDPDomain) { diff --git a/protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/json/EnumFixer.kt b/protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/preprocessing/EnumFixer.kt similarity index 98% rename from protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/json/EnumFixer.kt rename to protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/preprocessing/EnumFixer.kt index 5ab18570..e1c912e6 100644 --- a/protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/json/EnumFixer.kt +++ b/protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/preprocessing/EnumFixer.kt @@ -1,5 +1,6 @@ -package org.hildan.chrome.devtools.protocol.json +package org.hildan.chrome.devtools.protocol.preprocessing +import org.hildan.chrome.devtools.protocol.json.* import org.hildan.chrome.devtools.protocol.names.* // Workaround for https://github.com/ChromeDevTools/devtools-protocol/issues/244 diff --git a/protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/preprocessing/JsonPreProcessor.kt b/protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/preprocessing/JsonPreProcessor.kt new file mode 100644 index 00000000..3c11d718 --- /dev/null +++ b/protocol-generator/cdp-kotlin-generator/src/main/kotlin/org/hildan/chrome/devtools/protocol/preprocessing/JsonPreProcessor.kt @@ -0,0 +1,43 @@ +package org.hildan.chrome.devtools.protocol.preprocessing + +import org.hildan.chrome.devtools.protocol.json.JsonDomain +import org.hildan.chrome.devtools.protocol.json.JsonDomainParameter +import org.hildan.chrome.devtools.protocol.json.JsonDomainType + +/** + * Pre-processes the list of domains from the JSON definitions to work around some issues in the definitions themselves. + */ +internal fun List.preprocessed(): List = this + // Workaround for https://github.com/ChromeDevTools/devtools-protocol/issues/317 + .transformDomainTypeProperty("Network", "Cookie", "expires") { it.copy(optional = true) } + // Workaround for https://github.com/ChromeDevTools/devtools-protocol/issues/244 + .map { it.pullNestedEnumsToTopLevel() } + +private fun List.transformDomainTypeProperty( + domain: String, + type: String, + property: String, + transform: (JsonDomainParameter) -> JsonDomainParameter, +): List = transformDomain(domain) { d -> + d.transformType(type) { t -> + t.transformProperty(property, transform) + } +} + +private fun List.transformDomain( + name: String, + transform: (JsonDomain) -> JsonDomain, +): List = transformIf({ it.domain == name }) { transform(it) } + +private fun JsonDomain.transformType( + name: String, + transform: (JsonDomainType) -> JsonDomainType, +): JsonDomain = copy(types = types.transformIf({ it.type == name }) { transform(it) }) + +private fun JsonDomainType.transformProperty( + name: String, + transform: (JsonDomainParameter) -> JsonDomainParameter, +): JsonDomainType = copy(properties = properties.transformIf({ it.name == name }) { transform(it) }) + +private fun List.transformIf(predicate: (T) -> Boolean, transform: (T) -> T): List = + map { if (predicate(it)) transform(it) else it } diff --git a/src/commonMain/kotlin/org/hildan/chrome/devtools/extensions/CrossDomainExtensions.kt b/src/commonMain/kotlin/org/hildan/chrome/devtools/extensions/CrossDomainExtensions.kt index 211734b5..42a5803b 100644 --- a/src/commonMain/kotlin/org/hildan/chrome/devtools/extensions/CrossDomainExtensions.kt +++ b/src/commonMain/kotlin/org/hildan/chrome/devtools/extensions/CrossDomainExtensions.kt @@ -20,7 +20,9 @@ suspend fun PageSession.clickOnElement( clickDuration: Duration = 100.milliseconds, mouseButton: MouseButton = MouseButton.left, ) { - val box = dom.getBoxModel(selector) ?: error("Cannot click on element, no node found using selector '$selector'") + val box = dom.getBoxModel(selector) + ?: error("Cannot click on element, no node found using selector '$selector'. " + + "If the node might appear later, use PageSession.dom.awaitNodeBySelector(...) first.") val elementCenter = box.content.center input.dispatchMouseClick( diff --git a/src/jvmTest/kotlin/IntegrationTestBase.kt b/src/jvmTest/kotlin/IntegrationTestBase.kt index 6b3e27cc..4e805f47 100644 --- a/src/jvmTest/kotlin/IntegrationTestBase.kt +++ b/src/jvmTest/kotlin/IntegrationTestBase.kt @@ -1,6 +1,7 @@ import io.ktor.client.* import io.ktor.client.plugins.websocket.* import kotlinx.coroutines.* +import kotlinx.coroutines.flow.first import kotlinx.serialization.* import kotlinx.serialization.json.* import org.hildan.chrome.devtools.domains.accessibility.* @@ -8,6 +9,7 @@ import org.hildan.chrome.devtools.domains.backgroundservice.* import org.hildan.chrome.devtools.domains.dom.* import org.hildan.chrome.devtools.domains.domdebugger.* import org.hildan.chrome.devtools.domains.runtime.* +import org.hildan.chrome.devtools.extensions.clickOnElement import org.hildan.chrome.devtools.protocol.* import org.hildan.chrome.devtools.protocol.json.* import org.hildan.chrome.devtools.sessions.* @@ -291,6 +293,27 @@ abstract class IntegrationTestBase { } } + @OptIn(ExperimentalChromeApi::class) + @Test + open fun missingExpiresInCookie() { + runBlockingWithTimeout { + chromeWebSocket().use { browser -> + browser.newPage().use { page -> + page.goto("https://x.com") + page.network.enable() + coroutineScope { + launch { + // ensures we don't crash on deserialization + page.network.responseReceivedExtraInfoEvents().first() + } + page.dom.awaitNodeBySelector("a[href=\"/login\"]") + page.clickOnElement("a[href=\"/login\"]") + } + } + } + } + } + protected fun runBlockingWithTimeout(block: suspend CoroutineScope.() -> Unit) = runBlocking { withTimeout(1.minutes, block) } diff --git a/src/jvmTest/kotlin/ZenikaIntegrationTests.kt b/src/jvmTest/kotlin/ZenikaIntegrationTests.kt index c5b23fa8..c9f906b0 100644 --- a/src/jvmTest/kotlin/ZenikaIntegrationTests.kt +++ b/src/jvmTest/kotlin/ZenikaIntegrationTests.kt @@ -2,6 +2,7 @@ import org.testcontainers.containers.* import org.testcontainers.junit.jupiter.* import org.testcontainers.junit.jupiter.Container import org.testcontainers.utility.* +import kotlin.test.Ignore @Testcontainers class ZenikaIntegrationTests : LocalIntegrationTestBase() { @@ -23,4 +24,8 @@ class ZenikaIntegrationTests : LocalIntegrationTestBase() { override val wsConnectUrl: String get() = "http://localhost:${zenikaChrome.firstMappedPort}" + + @Ignore("The Zenika container seems out of data and still treats cookiePartitionKey as a string instead of object") + override fun missingExpiresInCookie() { + } }