Skip to content

Commit

Permalink
Add workaround for optional Cookie.expired field
Browse files Browse the repository at this point in the history
  • Loading branch information
joffrey-bion committed Feb 9, 2025
1 parent dbeb9fc commit 2194ce8
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.*

Expand All @@ -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 }
Expand All @@ -52,12 +51,12 @@ class Generator(
generateChildSessionsFiles(childTargets = targets.filterNot { it.kotlinName == "Browser" })
}

private fun loadProtocolDomains(): List<ChromeDPDomain> {
private fun readProtocolDomains(): List<ChromeDPDomain> {
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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<JsonDomain>.preprocessed(): List<JsonDomain> = 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<JsonDomain>.transformDomainTypeProperty(
domain: String,
type: String,
property: String,
transform: (JsonDomainParameter) -> JsonDomainParameter,
): List<JsonDomain> = transformDomain(domain) { d ->
d.transformType(type) { t ->
t.transformProperty(property, transform)
}
}

private fun List<JsonDomain>.transformDomain(
name: String,
transform: (JsonDomain) -> JsonDomain,
): List<JsonDomain> = 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 <T> List<T>.transformIf(predicate: (T) -> Boolean, transform: (T) -> T): List<T> =
map { if (predicate(it)) transform(it) else it }
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
23 changes: 23 additions & 0 deletions src/jvmTest/kotlin/IntegrationTestBase.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
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.*
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.*
Expand Down Expand Up @@ -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)
}
Expand Down
5 changes: 5 additions & 0 deletions src/jvmTest/kotlin/ZenikaIntegrationTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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() {
}
}

0 comments on commit 2194ce8

Please sign in to comment.