Skip to content

Commit 42f8cfb

Browse files
committed
Improve test server setup
Sometimes tests that relied on file:// protocol just hung in the Browserless environment. Using the HTTP test server to serve local resources seems to solve the problem, and is cleaner anyway.
1 parent 02c18e6 commit 42f8cfb

File tree

7 files changed

+85
-74
lines changed

7 files changed

+85
-74
lines changed

src/jvmTest/kotlin/BrowserlessLocalIntegrationTests.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ class BrowserlessLocalIntegrationTests : LocalIntegrationTestBase() {
2323
.withStartupTimeout(Duration.ofMinutes(5)) // sometimes more than the default 2 minutes on CI
2424
.withExposedPorts(3000)
2525
.withAccessToHost(true)
26-
.withCopyFileToContainer(MountableFile.forClasspathResource("/test-server-pages/"), "/test-server-pages/")
2726

2827
override val httpUrl: String
2928
get() = "http://${browserlessChromium.host}:${browserlessChromium.getMappedPort(3000)}"

src/jvmTest/kotlin/LocalIntegrationTestBase.kt

Lines changed: 83 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,34 @@ import kotlin.time.Duration.Companion.seconds
1313

1414
abstract class LocalIntegrationTestBase : IntegrationTestBase() {
1515

16-
protected suspend fun PageSession.gotoTestPageResource(resourcePath: String) {
17-
goto("file:///test-server-pages/$resourcePath")
18-
}
19-
2016
@OptIn(ExperimentalChromeApi::class)
2117
@Test
2218
fun basicFlow_fileScheme() = runTestWithRealTime {
23-
chromeWebSocket().use { browser ->
24-
val pageSession = browser.newPage()
25-
val targetId = pageSession.metaData.targetId
26-
27-
pageSession.use { page ->
28-
page.gotoTestPageResource("basic.html")
29-
assertEquals("Basic tab title", page.target.getTargetInfo().targetInfo.title)
30-
assertTrue(browser.hasTarget(targetId), "the new target should be listed")
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)")
3130
}
32-
assertFalse(browser.hasTarget(targetId), "the new target should be closed (not listed)")
3331
}
3432
}
3533

3634
@OptIn(ExperimentalChromeApi::class)
3735
@Test
3836
fun basicFlow_httpScheme() = runTestWithRealTime {
39-
withResourceServerForTestcontainers { baseUrl ->
37+
withResourceServerForTestcontainers {
4038
chromeWebSocket().use { browser ->
4139
val pageSession = browser.newPage()
4240
val targetId = pageSession.metaData.targetId
4341

4442
pageSession.use { page ->
45-
page.goto("$baseUrl/test-server-pages/basic.html")
43+
page.gotoTestPageResource("basic.html")
4644
assertEquals("Basic tab title", page.target.getTargetInfo().targetInfo.title)
4745
assertTrue(browser.hasTarget(targetId), "the new target should be listed")
4846
}
@@ -53,24 +51,26 @@ abstract class LocalIntegrationTestBase : IntegrationTestBase() {
5351

5452
@Test
5553
fun page_getTargets_fileScheme() = runTestWithRealTime {
56-
chromeWebSocket().use { browser ->
57-
browser.newPage().use { page ->
58-
page.gotoTestPageResource("basic.html")
59-
val targets = page.target.getTargets().targetInfos
60-
val targetInfo = targets.first { it.targetId == page.metaData.targetId }
61-
assertEquals("page", targetInfo.type)
62-
assertTrue(targetInfo.attached)
63-
assertTrue(targetInfo.url.contains("basic.html"))
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+
}
6464
}
6565
}
6666
}
6767

6868
@Test
6969
fun page_getTargets_httpScheme() = runTestWithRealTime {
70-
withResourceServerForTestcontainers { baseUrl ->
70+
withResourceServerForTestcontainers {
7171
chromeWebSocket().use { browser ->
7272
browser.newPage().use { page ->
73-
page.goto("$baseUrl/test-server-pages/basic.html")
73+
page.gotoTestPageResource("basic.html")
7474
val targets = page.target.getTargets().targetInfos
7575
val targetInfo = targets.first { it.targetId == page.metaData.targetId }
7676
assertEquals("page", targetInfo.type)
@@ -84,13 +84,13 @@ abstract class LocalIntegrationTestBase : IntegrationTestBase() {
8484
@OptIn(ExperimentalChromeApi::class)
8585
@Test
8686
fun page_goto() = runTestWithRealTime {
87-
withResourceServerForTestcontainers { baseUrl ->
87+
withResourceServerForTestcontainers {
8888
chromeWebSocket().use { browser ->
8989
browser.newPage().use { page ->
90-
page.goto("$baseUrl/test-server-pages/basic.html")
90+
page.gotoTestPageResource("basic.html")
9191
assertEquals("Basic tab title", page.target.getTargetInfo().targetInfo.title)
9292

93-
page.goto("$baseUrl/test-server-pages/other.html")
93+
page.gotoTestPageResource("other.html")
9494
assertEquals("Other tab title", page.target.getTargetInfo().targetInfo.title)
9595
val nodeId = withTimeoutOrNull(5.seconds) {
9696
page.dom.awaitNodeBySelector("p[class='some-p-class']")
@@ -110,58 +110,64 @@ abstract class LocalIntegrationTestBase : IntegrationTestBase() {
110110
@OptIn(ExperimentalChromeApi::class)
111111
@Test
112112
fun sessionThrowsIOExceptionIfAlreadyClosed() = runTestWithRealTime {
113-
val browser = chromeWebSocket()
114-
val session = browser.newPage()
115-
session.gotoTestPageResource("basic.html")
113+
withResourceServerForTestcontainers {
114+
val browser = chromeWebSocket()
115+
val session = browser.newPage()
116+
session.gotoTestPageResource("basic.html")
116117

117-
browser.close()
118+
browser.close()
118119

119-
assertFailsWith<RequestNotSentException> {
120-
session.target.getTargetInfo().targetInfo
120+
assertFailsWith<RequestNotSentException> {
121+
session.target.getTargetInfo().targetInfo
122+
}
121123
}
122124
}
123125

124126
@Test
125127
fun attributesAccess() = runTestWithRealTime {
126-
chromeWebSocket().use { browser ->
127-
browser.newPage().use { page ->
128-
page.gotoTestPageResource("select.html")
129-
130-
val nodeId = page.dom.findNodeBySelector("select[name=pets] option[selected]")
131-
assertNull(nodeId, "No option is selected in this <select>")
132-
133-
val attributes1 = page.dom.getTypedAttributes("select[name=pets] option[selected]")
134-
assertNull(attributes1, "No option is selected in this <select>")
135-
136-
val attributes2 = page.dom.getTypedAttributes("select[name=pets-selected] option[selected]")
137-
assertNotNull(attributes2, "There should be a selected option")
138-
assertEquals(true, attributes2.selected)
139-
assertEquals("cat", attributes2.value)
140-
val value = page.dom.getAttributeValue("select[name=pets-selected] option[selected]", "value")
141-
assertEquals("cat", value)
142-
// Attributes without value (e.g. "selected" in <option name="x" selected />) are returned as empty
143-
// strings by the protocol.
144-
val selected = page.dom.getAttributeValue("select[name=pets-selected] option[selected]", "selected")
145-
assertEquals("", selected)
146-
147-
val absentValue =
148-
page.dom.getAttributeValue("select[name=pets-selected-without-value] option[selected]", "value")
149-
assertNull(absentValue, "There is no 'value' attribute in this select option")
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+
}
150154
}
151155
}
152156
}
153157

154158
@OptIn(ExperimentalChromeApi::class)
155159
@Test
156160
fun test_deserialization_unknown_enum() = runTestWithRealTime {
157-
chromeWebSocket().use { browser ->
158-
browser.newPage().use { page ->
159-
page.gotoTestPageResource("basic.html")
160-
val tree = page.accessibility.getFullAXTree() // just test that this doesn't fail
161-
162-
assertTrue("we are no longer testing that unknown AXPropertyName values are deserialized as NotDefinedInProtocol") {
163-
tree.nodes.any { n ->
164-
n.properties.anyUndefinedName() || n.ignoredReasons.anyUndefinedName()
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+
}
165171
}
166172
}
167173
}
@@ -171,14 +177,14 @@ abstract class LocalIntegrationTestBase : IntegrationTestBase() {
171177
@OptIn(ExperimentalChromeApi::class)
172178
@Test
173179
fun test_deserialization_RemoteObject_subtype_enum() = runTestWithRealTime {
174-
withResourceServerForTestcontainers { baseUrl ->
180+
withResourceServerForTestcontainers {
175181
chromeWebSocket().use { browser ->
176182
browser.newPage().use { page ->
177183
page.runtime.enable()
178184
val deferredEvent = async(start = CoroutineStart.UNDISPATCHED) {
179185
page.runtime.consoleAPICalledEvents().first()
180186
}
181-
page.goto("$baseUrl/test-server-pages/trustedtype.html")
187+
page.gotoTestPageResource("trustedtype.html")
182188
val event = deferredEvent.await()
183189
// proof that we have to provide this subtype as enum value
184190
assertEquals(RemoteObjectSubtype.trustedtype, event.args.first().subtype)
@@ -187,10 +193,17 @@ abstract class LocalIntegrationTestBase : IntegrationTestBase() {
187193
}
188194
}
189195

190-
protected suspend fun withResourceServerForTestcontainers(block: suspend (baseUrl: String) -> Unit) {
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) {
191204
withResourceHttpServer { port ->
192205
Testcontainers.exposeHostPorts(port)
193-
block("http://host.testcontainers.internal:$port")
206+
TestServerContext("http://host.testcontainers.internal:$port").block()
194207
}
195208
}
196209

src/jvmTest/kotlin/ZenikaIntegrationTests.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ class ZenikaIntegrationTests : LocalIntegrationTestBase() {
2929
.withExposedPorts(9222)
3030
.withAccessToHost(true)
3131
.withCommand("--no-sandbox --remote-debugging-address=0.0.0.0 --remote-debugging-port=9222 about:blank")
32-
.withCopyFileToContainer(MountableFile.forClasspathResource("/test-server-pages/"), "/test-server-pages/")
3332

3433
override val httpUrl: String
3534
get() = "http://${zenikaChrome.host}:${zenikaChrome.getMappedPort(9222)}"
@@ -69,14 +68,14 @@ class ZenikaIntegrationTests : LocalIntegrationTestBase() {
6968
@OptIn(ExperimentalChromeApi::class)
7069
@Test
7170
fun parallelPages() = runTestWithRealTime {
72-
withResourceServerForTestcontainers { baseUrl ->
71+
withResourceServerForTestcontainers {
7372
chromeWebSocket().use { browser ->
7473
// we want all coroutines to finish before we close the browser session
7574
withContext(Dispatchers.IO) {
7675
repeat(20) {
7776
launch {
7877
browser.newPage().use { page ->
79-
page.goto("$baseUrl/test-server-pages/basic.html")
78+
page.gotoTestPageResource("basic.html")
8079
page.runtime.getHeapUsage()
8180
val docRoot = page.dom.getDocumentRootNodeId()
8281
page.dom.describeNode(DescribeNodeRequest(docRoot, depth = 2))

0 commit comments

Comments
 (0)