Skip to content

Commit 3bcbd83

Browse files
committed
Reusing the same test server for all tests (as JUnit extension)
This improves the performance of the tests (no longer 15s each), also makes for a better organization.
1 parent 42f8cfb commit 3bcbd83

File tree

3 files changed

+156
-142
lines changed

3 files changed

+156
-142
lines changed

src/jvmTest/kotlin/LocalIntegrationTestBase.kt

Lines changed: 113 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -6,168 +6,168 @@ import org.hildan.chrome.devtools.domains.dom.*
66
import org.hildan.chrome.devtools.domains.runtime.RemoteObjectSubtype
77
import org.hildan.chrome.devtools.protocol.*
88
import org.hildan.chrome.devtools.sessions.*
9+
import org.junit.jupiter.api.BeforeEach
910
import org.junit.jupiter.api.Test
11+
import org.junit.jupiter.api.extension.RegisterExtension
1012
import org.testcontainers.*
1113
import kotlin.test.*
1214
import kotlin.time.Duration.Companion.seconds
1315

1416
abstract class LocalIntegrationTestBase : IntegrationTestBase() {
1517

18+
companion object {
19+
@RegisterExtension
20+
val resourceServer = TestResourcesServerExtension()
21+
}
22+
23+
@BeforeEach
24+
fun register() {
25+
Testcontainers.exposeHostPorts(resourceServer.port)
26+
}
27+
28+
protected suspend fun PageSession.gotoTestPageResource(resourcePath: String) {
29+
goto("http://host.testcontainers.internal:${resourceServer.port}/$resourcePath")
30+
}
31+
1632
@OptIn(ExperimentalChromeApi::class)
1733
@Test
1834
fun basicFlow_fileScheme() = runTestWithRealTime {
19-
withResourceServerForTestcontainers {
20-
chromeWebSocket().use { browser ->
21-
val pageSession = browser.newPage()
22-
val targetId = pageSession.metaData.targetId
23-
24-
pageSession.use { page ->
25-
page.gotoTestPageResource("basic.html")
26-
assertEquals("Basic tab title", page.target.getTargetInfo().targetInfo.title)
27-
assertTrue(browser.hasTarget(targetId), "the new target should be listed")
28-
}
29-
assertFalse(browser.hasTarget(targetId), "the new target should be closed (not listed)")
35+
chromeWebSocket().use { browser ->
36+
val pageSession = browser.newPage()
37+
val targetId = pageSession.metaData.targetId
38+
39+
pageSession.use { page ->
40+
page.gotoTestPageResource("basic.html")
41+
assertEquals("Basic tab title", page.target.getTargetInfo().targetInfo.title)
42+
assertTrue(browser.hasTarget(targetId), "the new target should be listed")
3043
}
44+
assertFalse(browser.hasTarget(targetId), "the new target should be closed (not listed)")
3145
}
3246
}
3347

3448
@OptIn(ExperimentalChromeApi::class)
3549
@Test
3650
fun basicFlow_httpScheme() = runTestWithRealTime {
37-
withResourceServerForTestcontainers {
38-
chromeWebSocket().use { browser ->
39-
val pageSession = browser.newPage()
40-
val targetId = pageSession.metaData.targetId
41-
42-
pageSession.use { page ->
43-
page.gotoTestPageResource("basic.html")
44-
assertEquals("Basic tab title", page.target.getTargetInfo().targetInfo.title)
45-
assertTrue(browser.hasTarget(targetId), "the new target should be listed")
46-
}
47-
assertFalse(browser.hasTarget(targetId), "the new target should be closed (not listed)")
51+
chromeWebSocket().use { browser ->
52+
val pageSession = browser.newPage()
53+
val targetId = pageSession.metaData.targetId
54+
55+
pageSession.use { page ->
56+
page.gotoTestPageResource("basic.html")
57+
assertEquals("Basic tab title", page.target.getTargetInfo().targetInfo.title)
58+
assertTrue(browser.hasTarget(targetId), "the new target should be listed")
4859
}
60+
assertFalse(browser.hasTarget(targetId), "the new target should be closed (not listed)")
4961
}
5062
}
5163

5264
@Test
5365
fun page_getTargets_fileScheme() = runTestWithRealTime {
54-
withResourceServerForTestcontainers {
55-
chromeWebSocket().use { browser ->
56-
browser.newPage().use { page ->
57-
page.gotoTestPageResource("basic.html")
58-
val targets = page.target.getTargets().targetInfos
59-
val targetInfo = targets.first { it.targetId == page.metaData.targetId }
60-
assertEquals("page", targetInfo.type)
61-
assertTrue(targetInfo.attached)
62-
assertTrue(targetInfo.url.contains("basic.html"))
63-
}
66+
chromeWebSocket().use { browser ->
67+
browser.newPage().use { page ->
68+
page.gotoTestPageResource("basic.html")
69+
val targets = page.target.getTargets().targetInfos
70+
val targetInfo = targets.first { it.targetId == page.metaData.targetId }
71+
assertEquals("page", targetInfo.type)
72+
assertTrue(targetInfo.attached)
73+
assertTrue(targetInfo.url.contains("basic.html"))
6474
}
6575
}
6676
}
6777

6878
@Test
6979
fun page_getTargets_httpScheme() = runTestWithRealTime {
70-
withResourceServerForTestcontainers {
71-
chromeWebSocket().use { browser ->
72-
browser.newPage().use { page ->
73-
page.gotoTestPageResource("basic.html")
74-
val targets = page.target.getTargets().targetInfos
75-
val targetInfo = targets.first { it.targetId == page.metaData.targetId }
76-
assertEquals("page", targetInfo.type)
77-
assertTrue(targetInfo.attached)
78-
assertTrue(targetInfo.url.contains("basic.html"))
79-
}
80+
chromeWebSocket().use { browser ->
81+
browser.newPage().use { page ->
82+
page.gotoTestPageResource("basic.html")
83+
val targets = page.target.getTargets().targetInfos
84+
val targetInfo = targets.first { it.targetId == page.metaData.targetId }
85+
assertEquals("page", targetInfo.type)
86+
assertTrue(targetInfo.attached)
87+
assertTrue(targetInfo.url.contains("basic.html"))
8088
}
8189
}
8290
}
8391

8492
@OptIn(ExperimentalChromeApi::class)
8593
@Test
8694
fun page_goto() = runTestWithRealTime {
87-
withResourceServerForTestcontainers {
88-
chromeWebSocket().use { browser ->
89-
browser.newPage().use { page ->
90-
page.gotoTestPageResource("basic.html")
91-
assertEquals("Basic tab title", page.target.getTargetInfo().targetInfo.title)
92-
93-
page.gotoTestPageResource("other.html")
94-
assertEquals("Other tab title", page.target.getTargetInfo().targetInfo.title)
95-
val nodeId = withTimeoutOrNull(5.seconds) {
96-
page.dom.awaitNodeBySelector("p[class='some-p-class']")
97-
}
98-
assertNotNull(
99-
nodeId,
100-
"timed out while waiting for DOM node with attribute: p[class='some-p-class']"
101-
)
102-
103-
val getOuterHTMLResponse = page.dom.getOuterHTML(GetOuterHTMLRequest(nodeId = nodeId))
104-
assertTrue(getOuterHTMLResponse.outerHTML.contains("<p class=\"some-p-class\">"))
95+
chromeWebSocket().use { browser ->
96+
browser.newPage().use { page ->
97+
page.gotoTestPageResource("basic.html")
98+
assertEquals("Basic tab title", page.target.getTargetInfo().targetInfo.title)
99+
100+
page.gotoTestPageResource("other.html")
101+
assertEquals("Other tab title", page.target.getTargetInfo().targetInfo.title)
102+
val nodeId = withTimeoutOrNull(5.seconds) {
103+
page.dom.awaitNodeBySelector("p[class='some-p-class']")
105104
}
105+
assertNotNull(
106+
nodeId,
107+
"timed out while waiting for DOM node with attribute: p[class='some-p-class']"
108+
)
109+
110+
val getOuterHTMLResponse = page.dom.getOuterHTML(GetOuterHTMLRequest(nodeId = nodeId))
111+
assertTrue(getOuterHTMLResponse.outerHTML.contains("<p class=\"some-p-class\">"))
106112
}
107113
}
108114
}
109115

110116
@OptIn(ExperimentalChromeApi::class)
111117
@Test
112118
fun sessionThrowsIOExceptionIfAlreadyClosed() = runTestWithRealTime {
113-
withResourceServerForTestcontainers {
114-
val browser = chromeWebSocket()
115-
val session = browser.newPage()
116-
session.gotoTestPageResource("basic.html")
119+
val browser = chromeWebSocket()
120+
val session = browser.newPage()
121+
session.gotoTestPageResource("basic.html")
117122

118-
browser.close()
123+
browser.close()
119124

120-
assertFailsWith<RequestNotSentException> {
121-
session.target.getTargetInfo().targetInfo
122-
}
125+
assertFailsWith<RequestNotSentException> {
126+
session.target.getTargetInfo().targetInfo
123127
}
124128
}
125129

126130
@Test
127131
fun attributesAccess() = runTestWithRealTime {
128-
withResourceServerForTestcontainers {
129-
chromeWebSocket().use { browser ->
130-
browser.newPage().use { page ->
131-
page.gotoTestPageResource("select.html")
132-
133-
val nodeId = page.dom.findNodeBySelector("select[name=pets] option[selected]")
134-
assertNull(nodeId, "No option is selected in this <select>")
135-
136-
val attributes1 = page.dom.getTypedAttributes("select[name=pets] option[selected]")
137-
assertNull(attributes1, "No option is selected in this <select>")
138-
139-
val attributes2 = page.dom.getTypedAttributes("select[name=pets-selected] option[selected]")
140-
assertNotNull(attributes2, "There should be a selected option")
141-
assertEquals(true, attributes2.selected)
142-
assertEquals("cat", attributes2.value)
143-
val value = page.dom.getAttributeValue("select[name=pets-selected] option[selected]", "value")
144-
assertEquals("cat", value)
145-
// Attributes without value (e.g. "selected" in <option name="x" selected />) are returned as empty
146-
// strings by the protocol.
147-
val selected = page.dom.getAttributeValue("select[name=pets-selected] option[selected]", "selected")
148-
assertEquals("", selected)
149-
150-
val absentValue =
151-
page.dom.getAttributeValue("select[name=pets-selected-without-value] option[selected]", "value")
152-
assertNull(absentValue, "There is no 'value' attribute in this select option")
153-
}
132+
chromeWebSocket().use { browser ->
133+
browser.newPage().use { page ->
134+
page.gotoTestPageResource("select.html")
135+
136+
val nodeId = page.dom.findNodeBySelector("select[name=pets] option[selected]")
137+
assertNull(nodeId, "No option is selected in this <select>")
138+
139+
val attributes1 = page.dom.getTypedAttributes("select[name=pets] option[selected]")
140+
assertNull(attributes1, "No option is selected in this <select>")
141+
142+
val attributes2 = page.dom.getTypedAttributes("select[name=pets-selected] option[selected]")
143+
assertNotNull(attributes2, "There should be a selected option")
144+
assertEquals(true, attributes2.selected)
145+
assertEquals("cat", attributes2.value)
146+
val value = page.dom.getAttributeValue("select[name=pets-selected] option[selected]", "value")
147+
assertEquals("cat", value)
148+
// Attributes without value (e.g. "selected" in <option name="x" selected />) are returned as empty
149+
// strings by the protocol.
150+
val selected = page.dom.getAttributeValue("select[name=pets-selected] option[selected]", "selected")
151+
assertEquals("", selected)
152+
153+
val absentValue =
154+
page.dom.getAttributeValue("select[name=pets-selected-without-value] option[selected]", "value")
155+
assertNull(absentValue, "There is no 'value' attribute in this select option")
154156
}
155157
}
156158
}
157159

158160
@OptIn(ExperimentalChromeApi::class)
159161
@Test
160162
fun test_deserialization_unknown_enum() = runTestWithRealTime {
161-
withResourceServerForTestcontainers {
162-
chromeWebSocket().use { browser ->
163-
browser.newPage().use { page ->
164-
page.gotoTestPageResource("basic.html")
165-
val tree = page.accessibility.getFullAXTree() // just test that this doesn't fail
166-
167-
assertTrue("we are no longer testing that unknown AXPropertyName values are deserialized as NotDefinedInProtocol") {
168-
tree.nodes.any { n ->
169-
n.properties.anyUndefinedName() || n.ignoredReasons.anyUndefinedName()
170-
}
163+
chromeWebSocket().use { browser ->
164+
browser.newPage().use { page ->
165+
page.gotoTestPageResource("basic.html")
166+
val tree = page.accessibility.getFullAXTree() // just test that this doesn't fail
167+
168+
assertTrue("we are no longer testing that unknown AXPropertyName values are deserialized as NotDefinedInProtocol") {
169+
tree.nodes.any { n ->
170+
n.properties.anyUndefinedName() || n.ignoredReasons.anyUndefinedName()
171171
}
172172
}
173173
}
@@ -177,36 +177,20 @@ abstract class LocalIntegrationTestBase : IntegrationTestBase() {
177177
@OptIn(ExperimentalChromeApi::class)
178178
@Test
179179
fun test_deserialization_RemoteObject_subtype_enum() = runTestWithRealTime {
180-
withResourceServerForTestcontainers {
181-
chromeWebSocket().use { browser ->
182-
browser.newPage().use { page ->
183-
page.runtime.enable()
184-
val deferredEvent = async(start = CoroutineStart.UNDISPATCHED) {
185-
page.runtime.consoleAPICalledEvents().first()
186-
}
187-
page.gotoTestPageResource("trustedtype.html")
188-
val event = deferredEvent.await()
189-
// proof that we have to provide this subtype as enum value
190-
assertEquals(RemoteObjectSubtype.trustedtype, event.args.first().subtype)
180+
chromeWebSocket().use { browser ->
181+
browser.newPage().use { page ->
182+
page.runtime.enable()
183+
val deferredEvent = async(start = CoroutineStart.UNDISPATCHED) {
184+
page.runtime.consoleAPICalledEvents().first()
191185
}
186+
page.gotoTestPageResource("trustedtype.html")
187+
val event = deferredEvent.await()
188+
// proof that we have to provide this subtype as enum value
189+
assertEquals(RemoteObjectSubtype.trustedtype, event.args.first().subtype)
192190
}
193191
}
194192
}
195193

196-
class TestServerContext(val baseUrl: String) {
197-
198-
suspend fun PageSession.gotoTestPageResource(resourcePath: String) {
199-
goto("$baseUrl/$resourcePath")
200-
}
201-
}
202-
203-
protected suspend fun withResourceServerForTestcontainers(block: suspend TestServerContext.() -> Unit) {
204-
withResourceHttpServer { port ->
205-
Testcontainers.exposeHostPorts(port)
206-
TestServerContext("http://host.testcontainers.internal:$port").block()
207-
}
208-
}
209-
210194
private fun List<AXProperty>?.anyUndefinedName(): Boolean =
211195
this != null && this.any { it.name is AXPropertyName.NotDefinedInProtocol }
212196
}

src/jvmTest/kotlin/TestServer.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,38 @@
11
import com.sun.net.httpserver.*
2+
import org.junit.jupiter.api.extension.AfterAllCallback
3+
import org.junit.jupiter.api.extension.AfterEachCallback
4+
import org.junit.jupiter.api.extension.BeforeAllCallback
5+
import org.junit.jupiter.api.extension.BeforeEachCallback
6+
import org.junit.jupiter.api.extension.Extension
7+
import org.junit.jupiter.api.extension.ExtensionContext
8+
import org.testcontainers.Testcontainers
29
import java.io.*
310
import java.net.*
11+
import kotlin.properties.Delegates
12+
13+
class TestResourcesServerExtension : Extension, BeforeAllCallback, AfterAllCallback {
14+
private lateinit var httpServer: HttpServer
15+
16+
val port: Int
17+
get() = httpServer.address.port
18+
19+
override fun beforeAll(context: ExtensionContext?) {
20+
httpServer = HttpServer.create(InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 10)
21+
httpServer.createContext("/") { exchange ->
22+
if (exchange.requestMethod != "GET") {
23+
exchange.respondInvalidMethod(listOf("GET"))
24+
return@createContext
25+
}
26+
val resourcePath = exchange.requestURI.path.removePrefix("/")
27+
exchange.respondWithResource(resourcePath)
28+
}
29+
httpServer.start()
30+
}
31+
32+
override fun afterAll(context: ExtensionContext?) {
33+
httpServer.stop(0)
34+
}
35+
}
436

537
/**
638
* Creates an HTTP server that serves Java resources from the current program at '/'.

src/jvmTest/kotlin/ZenikaIntegrationTests.kt

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,17 @@ class ZenikaIntegrationTests : LocalIntegrationTestBase() {
6868
@OptIn(ExperimentalChromeApi::class)
6969
@Test
7070
fun parallelPages() = runTestWithRealTime {
71-
withResourceServerForTestcontainers {
72-
chromeWebSocket().use { browser ->
73-
// we want all coroutines to finish before we close the browser session
74-
withContext(Dispatchers.IO) {
75-
repeat(20) {
76-
launch {
77-
browser.newPage().use { page ->
78-
page.gotoTestPageResource("basic.html")
79-
page.runtime.getHeapUsage()
80-
val docRoot = page.dom.getDocumentRootNodeId()
81-
page.dom.describeNode(DescribeNodeRequest(docRoot, depth = 2))
82-
page.storage.getCookies()
83-
}
71+
chromeWebSocket().use { browser ->
72+
// we want all coroutines to finish before we close the browser session
73+
withContext(Dispatchers.IO) {
74+
repeat(20) {
75+
launch {
76+
browser.newPage().use { page ->
77+
page.gotoTestPageResource("basic.html")
78+
page.runtime.getHeapUsage()
79+
val docRoot = page.dom.getDocumentRootNodeId()
80+
page.dom.describeNode(DescribeNodeRequest(docRoot, depth = 2))
81+
page.storage.getCookies()
8482
}
8583
}
8684
}

0 commit comments

Comments
 (0)