): TypeSpec =
107 | TypeSpec.objectBuilder("${fcEnumClass.simpleName}Serializer").apply {
108 | addModifiers(KModifier.PRIVATE)
109 | superclass(ExtDeclarations.fcEnumSerializer.parameterizedBy(fcEnumClass))
110 | addSuperclassConstructorParameter("%T::class", fcEnumClass)
111 |
112 | addFunction(FunSpec.builder("fromCode").apply {
113 | addModifiers(KModifier.OVERRIDE)
114 | addParameter("code", String::class)
115 | returns(fcEnumClass)
116 | beginControlFlow("return when (code)")
117 | enumValues.forEach {
118 | addCode("%S -> %T\n", it, fcEnumClass.nestedClass(protocolEnumEntryNameToKotlinName(it)))
119 | }
120 | addCode("else -> %T(code)", fcEnumClass.nestedClass(UndefinedEnumEntryName))
121 | endControlFlow()
122 | }.build())
123 |
124 | addFunction(FunSpec.builder("codeOf").apply {
125 | addModifiers(KModifier.OVERRIDE)
126 | addParameter("value", fcEnumClass)
127 | returns(String::class)
128 | beginControlFlow("return when (value)")
129 | enumValues.forEach {
130 | addCode("is %T -> %S\n", fcEnumClass.nestedClass(protocolEnumEntryNameToKotlinName(it)), it)
131 | }
132 | addCode("is %T -> value.value", fcEnumClass.nestedClass(UndefinedEnumEntryName))
133 | endControlFlow()
134 | }.build())
135 | }.build()
136 |
137 | private fun protocolEnumEntryNameToKotlinName(protocolName: String) = protocolName.dashesToCamelCase()
138 |
139 | private fun String.dashesToCamelCase(): String = replace(Regex("""-(\w)""")) { it.groupValues[1].uppercase() }
140 |
141 | private fun DomainTypeDeclaration.toTypeAliasSpec(type: ChromeDPType.NamedRef): TypeAliasSpec =
142 | TypeAliasSpec.builder(names.declaredName, type.typeName).apply {
143 | addKDocAndStabilityAnnotations(element = this@toTypeAliasSpec)
144 | }.build()
145 |
--------------------------------------------------------------------------------
/src/jvmTest/kotlin/LocalIntegrationTestBase.kt:
--------------------------------------------------------------------------------
1 | import kotlinx.coroutines.*
2 | import kotlinx.coroutines.flow.first
3 | import org.hildan.chrome.devtools.*
4 | import org.hildan.chrome.devtools.domains.dom.*
5 | import org.hildan.chrome.devtools.domains.runtime.RemoteObjectSubtype
6 | import org.hildan.chrome.devtools.protocol.*
7 | import org.hildan.chrome.devtools.sessions.*
8 | import org.junit.jupiter.api.BeforeEach
9 | import org.junit.jupiter.api.Test
10 | import org.junit.jupiter.api.extension.RegisterExtension
11 | import org.testcontainers.*
12 | import kotlin.test.*
13 | import kotlin.time.Duration.Companion.seconds
14 |
15 | abstract class LocalIntegrationTestBase : IntegrationTestBase() {
16 |
17 | companion object {
18 | @RegisterExtension
19 | val resourceServer = TestResourcesServerExtension()
20 | }
21 |
22 | @BeforeEach
23 | fun register() {
24 | Testcontainers.exposeHostPorts(resourceServer.port)
25 | }
26 |
27 | protected suspend fun PageSession.gotoTestPageResource(resourcePath: String) {
28 | goto("http://host.testcontainers.internal:${resourceServer.port}/$resourcePath")
29 | }
30 |
31 | @OptIn(ExperimentalChromeApi::class)
32 | @Test
33 | fun basicFlow_fileScheme() = runTestWithRealTime {
34 | chromeWebSocket().use { browser ->
35 | val pageSession = browser.newPage()
36 | val targetId = pageSession.metaData.targetId
37 |
38 | pageSession.use { page ->
39 | page.gotoTestPageResource("basic.html")
40 | assertEquals("Basic tab title", page.target.getTargetInfo().targetInfo.title)
41 | assertTrue(browser.hasTarget(targetId), "the new target should be listed")
42 | }
43 | assertFalse(browser.hasTarget(targetId), "the new target should be closed (not listed)")
44 | }
45 | }
46 |
47 | @OptIn(ExperimentalChromeApi::class)
48 | @Test
49 | fun basicFlow_httpScheme() = runTestWithRealTime {
50 | chromeWebSocket().use { browser ->
51 | val pageSession = browser.newPage()
52 | val targetId = pageSession.metaData.targetId
53 |
54 | pageSession.use { page ->
55 | page.gotoTestPageResource("basic.html")
56 | assertEquals("Basic tab title", page.target.getTargetInfo().targetInfo.title)
57 | assertTrue(browser.hasTarget(targetId), "the new target should be listed")
58 | }
59 | assertFalse(browser.hasTarget(targetId), "the new target should be closed (not listed)")
60 | }
61 | }
62 |
63 | @Test
64 | fun page_getTargets_fileScheme() = runTestWithRealTime {
65 | chromeWebSocket().use { browser ->
66 | browser.newPage().use { page ->
67 | page.gotoTestPageResource("basic.html")
68 | val targets = page.target.getTargets().targetInfos
69 | val targetInfo = targets.first { it.targetId == page.metaData.targetId }
70 | assertEquals("page", targetInfo.type)
71 | assertTrue(targetInfo.attached)
72 | assertTrue(targetInfo.url.contains("basic.html"))
73 | }
74 | }
75 | }
76 |
77 | @Test
78 | fun page_getTargets_httpScheme() = runTestWithRealTime {
79 | chromeWebSocket().use { browser ->
80 | browser.newPage().use { page ->
81 | page.gotoTestPageResource("basic.html")
82 | val targets = page.target.getTargets().targetInfos
83 | val targetInfo = targets.first { it.targetId == page.metaData.targetId }
84 | assertEquals("page", targetInfo.type)
85 | assertTrue(targetInfo.attached)
86 | assertTrue(targetInfo.url.contains("basic.html"))
87 | }
88 | }
89 | }
90 |
91 | @OptIn(ExperimentalChromeApi::class)
92 | @Test
93 | fun page_goto() = runTestWithRealTime {
94 | chromeWebSocket().use { browser ->
95 | browser.newPage().use { page ->
96 | page.gotoTestPageResource("basic.html")
97 | assertEquals("Basic tab title", page.target.getTargetInfo().targetInfo.title)
98 |
99 | page.gotoTestPageResource("other.html")
100 | assertEquals("Other tab title", page.target.getTargetInfo().targetInfo.title)
101 | val nodeId = withTimeoutOrNull(5.seconds) {
102 | page.dom.awaitNodeBySelector("p[class='some-p-class']")
103 | }
104 | assertNotNull(
105 | nodeId,
106 | "timed out while waiting for DOM node with attribute: p[class='some-p-class']"
107 | )
108 |
109 | val getOuterHTMLResponse = page.dom.getOuterHTML(GetOuterHTMLRequest(nodeId = nodeId))
110 | assertTrue(getOuterHTMLResponse.outerHTML.contains(""))
111 | }
112 | }
113 | }
114 |
115 | @OptIn(ExperimentalChromeApi::class)
116 | @Test
117 | fun sessionThrowsIOExceptionIfAlreadyClosed() = runTestWithRealTime {
118 | val browser = chromeWebSocket()
119 | val session = browser.newPage()
120 | session.gotoTestPageResource("basic.html")
121 |
122 | browser.close()
123 |
124 | assertFailsWith {
125 | session.target.getTargetInfo().targetInfo
126 | }
127 | }
128 |
129 | @Test
130 | fun attributesAccess() = runTestWithRealTime {
131 | chromeWebSocket().use { browser ->
132 | browser.newPage().use { page ->
133 | page.gotoTestPageResource("select.html")
134 |
135 | val nodeId = page.dom.findNodeBySelector("select[name=pets] option[selected]")
136 | assertNull(nodeId, "No option is selected in this