Created
May 5, 2021 14:29
-
-
Save loxal/1d9486a8d3a0c66ad1e766e7f39890c7 to your computer and use it in GitHub Desktop.
Testing web UI via HTML DOM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env kotlin | |
/* | |
* Copyright 2021 Alexander Orlov <alexander.orlov@loxal.net>. All rights reserved. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* https://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
@file:DependsOn("org.jetbrains.kotlin:kotlin-test-junit5:1.4.32") | |
@file:DependsOn("io.ktor:ktor-http-jvm:1.5.4") | |
@file:DependsOn("org.slf4j:slf4j-nop:1.7.30") | |
@file:DependsOn("com.squareup.okhttp3:okhttp:5.0.0-alpha.2") | |
@file:DependsOn("org.seleniumhq.selenium:selenium-chrome-driver:4.0.0-beta-3") | |
@file:DependsOn("org.seleniumhq.selenium:selenium-java:4.0.0-beta-3") | |
import io.ktor.http.* | |
import okhttp3.OkHttpClient | |
import okhttp3.Request | |
import org.openqa.selenium.* | |
import org.openqa.selenium.chrome.ChromeDriver | |
import org.openqa.selenium.chrome.ChromeOptions | |
import org.openqa.selenium.support.ui.ExpectedConditions | |
import org.openqa.selenium.support.ui.WebDriverWait | |
import java.time.Duration | |
import java.util.concurrent.TimeUnit | |
import kotlin.system.exitProcess | |
import kotlin.test.assertEquals | |
import kotlin.test.assertFalse | |
import kotlin.test.assertNotNull | |
import kotlin.test.assertTrue | |
println("TENANT: ${System.getenv("TENANT")}") | |
var targetUrl: String | |
var crawlUrl: String | |
println("args.size: ${args.size}") | |
if (args.isNotEmpty()) { | |
println("args: ${args.joinToString()}") | |
targetUrl = args[0] | |
crawlUrl = args[1] | |
println("targetUrl: $targetUrl") | |
println("crawlUrl: $crawlUrl") | |
} else { | |
println("targetUrl required") | |
exitProcess(1) | |
} | |
private val browserOptions = ChromeOptions() | |
.setHeadless(true).addArguments("--no-sandbox") | |
val browser: WebDriver = ChromeDriver(browserOptions) | |
val site = browser.get("$targetUrl/app/gadget/main.html") | |
val gadgetDefaultSiteId = "a2e8d60b-0696-47ea-bc48-982598ee35bd" | |
val integrationCode: WebElement = browser.findElement(By.cssSelector("#integrationCode")) | |
val integrationCodeLength = 627 | |
assertNotNull(site) | |
private val jsExec = browser as JavascriptExecutor | |
fun curlTest() { | |
val httpClient = OkHttpClient.Builder() | |
.followRedirects(false) | |
.followSslRedirects(false) | |
.build() | |
val request = Request.Builder() | |
.url(targetUrl) | |
.build() | |
httpClient.newCall(request).execute().use { response -> | |
assertEquals(HttpStatusCode.OK.value, response.code) | |
val content = response.body?.string() | |
assertTrue(content!!.contains("<title>About</title>")) | |
assertTrue(content.contains("<script defer src=\"/app/client/webpack-bundle.min.js\"></script>")) | |
assertTrue(1000 < content.length) | |
} | |
val adminUiRequest = Request.Builder() | |
.url("$targetUrl/imprint.html") | |
.build() | |
httpClient.newCall(adminUiRequest).execute().use { response -> | |
assertEquals(HttpStatusCode.OK.value, response.code) | |
val content = response.body?.string() | |
assertTrue(3_000 < content!!.length) | |
} | |
val searchUiRequest = Request.Builder() | |
.url("$targetUrl/app/gadget/main.html") | |
.build() | |
httpClient.newCall(searchUiRequest).execute().use { response -> | |
assertEquals(HttpStatusCode.OK.value, response.code) | |
val content = response.body?.string() | |
assertTrue(11_000 < content!!.length) | |
} | |
println("=== curlTest ===") | |
} | |
fun sisEvaluationJourney() { | |
val notification = browser.findElement(By.cssSelector("#notificationCenter")) | |
val url = browser.findElement(By.cssSelector("#url")) | |
val resetEvaluationView = browser.findElement(By.cssSelector("#resetEvaluationView")) | |
val triggerButton = browser.findElement(By.cssSelector("#triggerButton")) | |
val termsAccepted = browser.findElement(By.cssSelector("#termsAccepted")) | |
val email = browser.findElement(By.cssSelector("#email")) | |
val integrationUrl = browser.findElement(By.cssSelector("#integrationUrl")) | |
val setupUrl = browser.findElement(By.cssSelector("#setupUrl")) | |
fun beforeCrawling() { | |
url.clear() | |
url.sendKeys(crawlUrl) | |
email.sendKeys("ops@loxal.net") | |
assertEquals("ops@loxal.net", email.getAttribute("value")) | |
assertTrue(setupUrl.getAttribute("value").isEmpty()) | |
assertFalse(resetEvaluationView.isDisplayed) | |
assertTrue(triggerButton.isEnabled) | |
assertFalse(triggerButton.isSelected) | |
assertTrue(triggerButton.isDisplayed) | |
assertEquals( | |
"Read and accept the Terms & Conditions above to continue", | |
triggerButton.getAttribute("textContent").trim() | |
) | |
assertTrue(15 < integrationUrl.getAttribute("href").length) | |
assertFalse(integrationUrl.getAttribute("href").contains("siteId=")) | |
assertEquals(integrationCodeLength, integrationCode.getAttribute("value").length) | |
assertTrue(integrationCode.getAttribute("value").contains("$gadgetDefaultSiteId\" defer=")) | |
assertFalse(email.isSelected) | |
assertTrue(email.isEnabled) | |
assertTrue(email.isDisplayed) | |
termsAccepted.click() | |
} | |
beforeCrawling() | |
fun afterCrawling() { | |
assertEquals( | |
"Crawl your website and search it as it is being crawled!", | |
triggerButton.getAttribute("textContent").trim() | |
) | |
triggerButton.click() | |
assertFalse(email.isSelected) | |
assertTrue(email.isEnabled) | |
assertTrue(email.isDisplayed) | |
WebDriverWait(browser, Duration.ofSeconds(15)).until( | |
ExpectedConditions.attributeContains( | |
email, "readOnly", "true" | |
) | |
) | |
assertTrue(url.getAttribute("readOnly").toBoolean()) | |
assertEquals("ops@loxal.net", email.getAttribute("value")) | |
val notificationProgress = WebDriverWait(browser, Duration.ofSeconds(15)).until( | |
ExpectedConditions.attributeContains( | |
notification, | |
"textContent", | |
"Crawler is running... please give us just a minute or two." | |
) | |
) | |
WebDriverWait(browser, Duration.ofSeconds(15)).until( | |
ExpectedConditions.elementToBeClickable(resetEvaluationView) | |
) | |
assertTrue(resetEvaluationView.isDisplayed) | |
assertEquals( | |
"Reset evaluation view and crawl another site", | |
resetEvaluationView.getAttribute("textContent").trim() | |
) | |
val triggerButtonStatus = WebDriverWait(browser, Duration.ofSeconds(15)) | |
.until( | |
ExpectedConditions.attributeContains( | |
triggerButton, | |
"textContent", | |
"Search your website below or crawl another site above" | |
) | |
) | |
assertFalse(triggerButton.isEnabled) // true/false flipping | |
assertFalse(triggerButton.isSelected) | |
assertFalse(triggerButton.isDisplayed) | |
val notificationSuccess = WebDriverWait(browser, Duration.ofSeconds(15)) | |
.until( | |
ExpectedConditions.attributeContains( | |
notification, | |
"textContent", | |
"1 pages from https://example.com have been crawled." | |
) | |
) | |
assertTrue(notification.isDisplayed) | |
notification.click() | |
assertFalse(notification.isDisplayed) | |
assertTrue(setupUrl.getAttribute("value").startsWith("https://loxal.net/app/gadget/main.html?siteId=")) | |
assertFalse(setupUrl.getAttribute("value").contains(gadgetDefaultSiteId)) | |
assertEquals(154, setupUrl.getAttribute("value").length) | |
assertTrue(notificationProgress.and(notificationSuccess).and(triggerButtonStatus)) | |
assertEquals(crawlUrl, url.getAttribute("value")) | |
assertTrue(150 < integrationUrl.getAttribute("href").length) | |
assertTrue(integrationUrl.getAttribute("href").contains("siteId=")) | |
assertTrue(integrationUrl.getAttribute("href").contains("&url=$crawlUrl")) | |
assertEquals(integrationCodeLength - 9, integrationCode.getAttribute("value").length) | |
assertFalse(integrationCode.getAttribute("value").contains(gadgetDefaultSiteId)) | |
} | |
afterCrawling() | |
} | |
fun testPageFinder( | |
finderShadowRootHostId: String = "finder-searchbar", | |
finderPlaceholder: String = "\uD83D\uDD0D Enter * to search for anything", | |
autocompleteFinding: String = "Example Domain This domain is for…", | |
searchFinding: String = "Example DomainExample Domain This domain is for use in illustrative examples in documents.https://example.com/" | |
) { | |
val finderShadowRootHost = browser.findElement(By.cssSelector("#${finderShadowRootHostId}")) | |
assertEquals("div", finderShadowRootHost.tagName) | |
assertEquals(finderShadowRootHostId, finderShadowRootHost.getAttribute("id")) | |
assertTrue(finderShadowRootHost.isEnabled) | |
assertTrue(finderShadowRootHost.isDisplayed) | |
assertFalse(finderShadowRootHost.isSelected) | |
val finderShadowRoot = jsExec.executeScript( | |
"return arguments[0].shadowRoot.querySelector('#sitesearch-page-finder > input[type=search]')", | |
finderShadowRootHost | |
) as WebElement | |
println(finderShadowRoot.getAttribute("placeholder")) | |
assertEquals(finderPlaceholder, finderShadowRoot.getAttribute("placeholder")) | |
assertTrue(finderShadowRoot.getAttribute("value").isEmpty()) | |
finderShadowRoot.sendKeys("example") | |
assertEquals("example", finderShadowRoot.getAttribute("value")) | |
TimeUnit.SECONDS.sleep(1) | |
val finderShadowAutocomplete = jsExec.executeScript( | |
"return arguments[0].shadowRoot.querySelector('#sitesearch-page-finder > dl')", | |
finderShadowRootHost | |
) as WebElement | |
WebDriverWait(browser, Duration.ofSeconds(15)).until( | |
ExpectedConditions.attributeContains( | |
finderShadowAutocomplete, "textContent", autocompleteFinding | |
) | |
) | |
finderShadowRoot.sendKeys(Keys.ENTER) | |
TimeUnit.SECONDS.sleep(1) | |
val finderShadowSearch = jsExec.executeScript( | |
"return arguments[0].shadowRoot.querySelector('#sitesearch-page-finder > dl > div')", | |
finderShadowRootHost | |
) as WebElement | |
assertTrue( | |
finderShadowSearch.getAttribute("textContent").startsWith(searchFinding), | |
finderShadowSearch.getAttribute("textContent") | |
) | |
println("=== testPageFinder for #${finderShadowRootHostId} ===") | |
} | |
fun validateGadget() { | |
assertTrue(browser.currentUrl.endsWith("/app/gadget/main.html")) | |
val subscriptionLink = browser.findElement(By.cssSelector("#sis-gadget-choose-subscription")) | |
subscriptionLink.click() | |
assertTrue(browser.currentUrl.endsWith("/pricing-site-search.html")) | |
} | |
fun afterRevisiting() { | |
browser.navigate().refresh() | |
// browser.get("$targetUrl/app/gadget/main.html") | |
// TimeUnit.SECONDS.sleep(15) | |
val integrationUrl = browser.findElement(By.cssSelector("#integrationUrl")) | |
val integrationCode = browser.findElement(By.cssSelector("#integrationCode")) | |
val setupUrl = browser.findElement(By.cssSelector("#setupUrl")) | |
assertFalse(integrationCode.getAttribute("value").contains(gadgetDefaultSiteId)) | |
assertEquals(integrationCodeLength - 9, integrationCode.getAttribute("value").length) | |
assertTrue(integrationUrl.getAttribute("href").startsWith("https://loxal.net/app/gadget/main.html?siteId=")) | |
assertFalse(integrationUrl.getAttribute("href").contains(gadgetDefaultSiteId)) | |
assertTrue(setupUrl.getAttribute("value").startsWith("https://loxal.net/app/gadget/main.html?siteId=")) | |
assertFalse(setupUrl.getAttribute("value").contains(gadgetDefaultSiteId)) | |
val resetEvaluationView = browser.findElement(By.cssSelector("#resetEvaluationView")) | |
val termsAccepted = browser.findElement(By.cssSelector("#termsAccepted")) | |
assertEquals("Reset evaluation view and crawl another site", resetEvaluationView.text) | |
assertTrue(termsAccepted.isDisplayed) | |
assertFalse(termsAccepted.isEnabled) | |
assertTrue(termsAccepted.isSelected) | |
assertTrue(termsAccepted.getAttribute("checked").toBoolean()) | |
} | |
//val testPageFinderSearchFinding = "News that move the IT, tomorrow." | |
val testPageFinderSearchFinding = "Effective pinpoint solutions that just work}" | |
try { | |
curlTest() | |
sisEvaluationJourney() | |
testPageFinder( | |
finderShadowRootHostId = "searchbar", | |
finderPlaceholder = "\uD83D\uDD0DFind...", | |
autocompleteFinding = "acrobat-example.pdf", | |
searchFinding = testPageFinderSearchFinding | |
) // Site, before gadget triggered | |
testPageFinder() // Gadget | |
afterRevisiting() | |
testPageFinder( | |
finderShadowRootHostId = "searchbar", | |
finderPlaceholder = "\uD83D\uDD0DFind...", | |
autocompleteFinding = "acrobat-example.pdf", | |
searchFinding = testPageFinderSearchFinding | |
) // Site, after gadget triggered | |
validateGadget() | |
} finally { | |
browser.quit() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment