Skip to content

Commit

Permalink
[web]
Browse files Browse the repository at this point in the history
- add [browser metadata](../commands/web/index#browser-metadata) as System variable `nexial.browser.meta`.

Signed-off-by: automike <[email protected]>
  • Loading branch information
mikeliucc committed Dec 15, 2019
1 parent 730eef1 commit 8b73871
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 87 deletions.
1 change: 1 addition & 0 deletions src/main/java/org/nexial/core/NexialConst.java
Original file line number Diff line number Diff line change
Expand Up @@ -1515,6 +1515,7 @@ public static final class Web {
public static final String BROWSER_INCOGNITO = registerSysVar(NS_BROWSER + "." + KEY_INCOGNITO, true);
public static final String OPT_LAST_ALERT_TEXT = registerSysVar(NAMESPACE + "lastAlertText");
public static final String OPT_ALERT_IGNORE_FLAG = registerSysVar(NAMESPACE + "ignoreBrowserAlert", false);
public static final String BROWSER_META = registerSysVar(NS_BROWSER + ".meta");

// metrics
public static final String NS_WEB_METRICS = registerSysVarGroup(NS_WEB + "metrics.");
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/nexial/core/plugins/web/Browser.java
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,8 @@ protected void shutdown() {
proxy = null;
}

if (context != null) { context.removeData(BROWSER_META); }

clearWinHandles();

if (driver == null) { return; }
Expand Down
63 changes: 44 additions & 19 deletions src/main/java/org/nexial/core/plugins/web/WebCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.nexial.commons.utils.web.URLEncodingUtils;
import org.nexial.core.WebProxy;
import org.nexial.core.browsermob.ProxyHandler;
import org.nexial.core.model.BrowserMeta;
import org.nexial.core.model.ExecutionContext;
import org.nexial.core.model.NexialUrlInvokedEvent;
import org.nexial.core.model.StepResult;
Expand Down Expand Up @@ -1199,10 +1200,7 @@ public StepResult openAndWait(String url, String waitMs) {
url = validateUrl(url);
driver.get(url);
waitForBrowserStability(toPositiveLong(waitMs, "waitMs"));
updateWinHandle();
resizeSafariAfterOpen();

EventTracker.track(new NexialUrlInvokedEvent(browser.getBrowserType().name(), url));
postOpen(url);

return StepResult.success("opened URL " + hideAuthDetails(url));
}
Expand All @@ -1220,11 +1218,7 @@ public StepResult openHttpBasic(String url, String username, String password) {
StringUtils.substringAfter(url, "://");
driver.get(urlBasic);
waitForBrowserStability(context.getPollWaitMs());

updateWinHandle();
resizeSafariAfterOpen();

EventTracker.track(new NexialUrlInvokedEvent(browser.getBrowserType().name(), url));
postOpen(url);

return StepResult.success("opened URL " + hideAuthDetails(urlBasic));
}
Expand Down Expand Up @@ -1261,9 +1255,7 @@ public StepResult openIgnoreTimeout(String url) {

stopWatch.stop();

resizeSafariAfterOpen();

EventTracker.track(new NexialUrlInvokedEvent(browser.getBrowserType().name(), url));
postOpen(url);

return StepResult.success("opened URL " + hideAuthDetails(url));
}
Expand Down Expand Up @@ -1709,14 +1701,23 @@ public StepResult screenshot(String file, String locator) {
public StepResult saveBrowserVersion(String var) {
requiresValidAndNotReadOnlyVariableName(var);

String ua = Objects.toString(jsExecutor.executeScript("return navigator.userAgent;"));

String apiKey = context.getStringData(USERSTACK_APIKEY);
UserStackAPI usApi = StringUtils.isNotBlank(apiKey) ? new UserStackAPI(apiKey) : new UserStackAPI();
Map<String, String> uaMap = usApi.detect(ua);
if (!context.hasData(BROWSER_META)) {
syncBrowserMeta();
if (!context.hasData(BROWSER_META)) {
// we've tried... forget it
return StepResult.fail("Unable to fetch browser version; " +
"browser or underlying webdriver possibly not initialized");
}
}

context.setData(var, uaMap.get("browser"));
return StepResult.success("Browser version saved to data variable '" + var + "'");
BrowserMeta browserMeta = (BrowserMeta) context.getObjectData(BROWSER_META);
if (browserMeta != null) {
context.setData(var, browserMeta.browser());
return StepResult.success("Browser version saved to data variable '" + var + "'");
} else {
return StepResult.fail("Unable to fetch browser version; " +
"browser or underlying webdriver possibly not initialized");
}
}

@Override
Expand Down Expand Up @@ -1915,6 +1916,30 @@ public void collectClientPerfMetrics() {
}
}

protected void postOpen(String url) {
updateWinHandle();
resizeSafariAfterOpen();
EventTracker.track(new NexialUrlInvokedEvent(browser.getBrowserType().name(), url));
syncBrowserMeta();
}

protected void syncBrowserMeta() {
Object browserMeta = context.getObjectData(BROWSER_META);
if (browserMeta instanceof BrowserMeta) { return; }

if (jsExecutor == null || driver == null) {
ConsoleUtils.error("Browser or webdriver not yet initialized; cancel the fetching of browser meta...");
return;
}

// go get it
String apiKey = context.getStringData(USERSTACK_APIKEY);
UserStackAPI userStackAPI = StringUtils.isNotBlank(apiKey) ? new UserStackAPI(apiKey) : new UserStackAPI();
String ua = Objects.toString(jsExecutor.executeScript("return navigator.userAgent;"));
browserMeta = userStackAPI.detectAsBrowserMeta(ua);
context.setData(BROWSER_META, browserMeta);
}

// todo: was called from screenshot().. still need it?
@NotNull
protected static BufferedImage newBlankImage(int width, int height) {
Expand Down
60 changes: 60 additions & 0 deletions src/main/kotlin/org/nexial/core/model/BrowserMeta.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* 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
*
* http://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.
*/

package org.nexial.core.model

/**
* encapsulation of what userstack returns back based on user-agent string. We are cherry-picking only the meaningful
* ones, which will be stored in ExecutionContent
*/
data class BrowserMeta(val name: String,
val version: String,
val userAgent: String,
val os: BrowserOS,
val device: BrowserDevice) {

fun browser() = ("$name $version").trim()
}

data class BrowserOS(val name: String, val code: String, val family: String)

data class BrowserDevice(val type: BrowserDeviceType, val brand: String?, val name: String?)

enum class BrowserDeviceType {
desktop {
override fun isMobileDevice() = false
},
tablet {
override fun isMobileDevice() = true
},
smartphone {
override fun isMobileDevice() = true
},
console {
override fun isMobileDevice() = false
},
smarttv {
override fun isMobileDevice() = false
},
wearable {
override fun isMobileDevice() = true
},
unknown {
override fun isMobileDevice() = false
};

abstract fun isMobileDevice(): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import org.apache.commons.lang3.BooleanUtils
import org.apache.commons.lang3.StringUtils
import org.nexial.commons.utils.FileUtil
import org.nexial.commons.utils.ResourceUtils
import org.nexial.core.NexialConst.Data.USERSTACK_APIKEY
import org.nexial.core.NexialConst.GSON
import org.nexial.core.NexialConst.Web.BROWSER_META
import org.nexial.core.NexialConst.Web.NS_WEB_METRICS
import org.nexial.core.model.BrowserMeta
import org.nexial.core.model.ExecutionContext
import org.nexial.core.model.TestStep
import org.nexial.core.utils.ConsoleUtils
Expand All @@ -34,7 +35,6 @@ import java.io.File
import java.io.FileReader
import java.io.FileWriter
import java.io.IOException
import java.util.*

class ClientPerformanceCollector(val command: WebCommand, private val output: String) {
private val resourceBase = "/org/nexial/core/plugins/web/metrics/"
Expand Down Expand Up @@ -119,16 +119,17 @@ class ClientPerformanceCollector(val command: WebCommand, private val output: St
}

private fun newExecution(context: ExecutionContext): JsonObject {
val apiKey = System.getProperty(USERSTACK_APIKEY)
val ua = Objects.toString(command.jsExecutor.executeScript("return navigator.userAgent;"))
val uaMap = (if (StringUtils.isNotBlank(apiKey)) UserStackAPI(apiKey) else UserStackAPI()).detect(ua)

val execution = JsonObject()
execution.addProperty("runID", context.runId)
execution.addProperty("project", context.project.name)
execution.addProperty("browser", uaMap["browser"])
execution.addProperty("os", uaMap["os"])
execution.add("scripts", JsonArray())

if (context.hasData(BROWSER_META)) {
val browserMeta = context.getObjectData(BROWSER_META) as BrowserMeta
execution.addProperty("browser", "${browserMeta.name} ${browserMeta.version}")
execution.addProperty("os", browserMeta.os.name)
}

return execution
}

Expand Down
64 changes: 26 additions & 38 deletions src/main/kotlin/org/nexial/core/plugins/web/UserStackAPI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,43 @@

package org.nexial.core.plugins.web

import com.google.gson.JsonNull
import com.google.gson.JsonObject
import org.nexial.core.NexialConst.GSON
import org.nexial.core.model.BrowserDevice
import org.nexial.core.model.BrowserDeviceType
import org.nexial.core.model.BrowserMeta
import org.nexial.core.model.BrowserOS
import org.nexial.core.plugins.ws.WebServiceClient

class UserStackAPI(apiKey: String = "5b71975a107de30d26f3878fa9adbb5e") {
private val apiURL = "http://api.userstack.com/detect?access_key=${apiKey}&ua="

fun detect(ua: String): Map<String, String> {
@Throws(IllegalArgumentException::class)
fun detectAsBrowserMeta(ua: String): BrowserMeta {
val response = WebServiceClient(null).get(apiURL + ua, null)
return if (response.returnCode in 200..299) {
parseBrowserMeta(response.body)
return if (response.returnCode == 200) {
val json = GSON.fromJson(response.body, JsonObject::class.java)
parseBrowserMeta(json, ua)
} else {
val map = mutableMapOf<String, String>()
map["error"] = response.statusText
map
throw IllegalArgumentException(response.statusText)
}
}

companion object {
@JvmStatic
fun parseBrowserMeta(jsonString: String): Map<String, String> {
val map = mutableMapOf<String, String>()

val json = GSON.fromJson(jsonString, JsonObject::class.java)
map["os"] = json.getAsJsonObject("os").getAsJsonPrimitive("name").asString

val browser = json.getAsJsonObject("browser")
val name = if (browser.has("name")) {
val browserName = browser.get("name")
if (browserName == null || browserName is JsonNull) {
"UNKNOWN BROWSER"
} else {
browser.getAsJsonPrimitive("name").asString
}
} else "UNKNOWN BROWSER"

val version = if (browser.has("version")) {
val versionElem = browser.get("version")
if (versionElem == null || versionElem is JsonNull) {
""
} else {
" " + browser.getAsJsonPrimitive("version").asString
}
} else ""

map["browser"] = "$name$version"

return map
}
internal fun parseBrowserMeta(json: JsonObject, ua: String): BrowserMeta {
val jsonOS = json.getAsJsonObject("os")
val jsonDevice = json.getAsJsonObject("device")
val browser = json.getAsJsonObject("browser")

val version = if (browser.get("version").isJsonNull) "" else browser.getAsJsonPrimitive("version").asString
val deviceBrand = if (jsonDevice.get("brand").isJsonNull) "" else jsonDevice.getAsJsonPrimitive("brand").asString
val deviceName = if (jsonDevice.get("name").isJsonNull) "" else jsonDevice.getAsJsonPrimitive("name").asString

return BrowserMeta(browser.getAsJsonPrimitive("name").asString, version, ua,
BrowserOS(jsonOS.getAsJsonPrimitive("name").asString,
jsonOS.getAsJsonPrimitive("code").asString,
jsonOS.getAsJsonPrimitive("family").asString),
BrowserDevice(BrowserDeviceType.valueOf(jsonDevice.getAsJsonPrimitive("type").asString),
deviceBrand,
deviceName))
}
}
36 changes: 14 additions & 22 deletions src/test/kotlin/org/nexial/core/plugins/web/UserStackAPITest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,19 @@

package org.nexial.core.plugins.web

import org.junit.After
import com.google.gson.JsonObject
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.nexial.core.NexialConst.GSON

class UserStackAPITest {

@Before
fun setUp() {
}

@After
fun tearDown() {
}

@Test
fun parseBrowserMeta_electron() {
val ua = """Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit\/537.36 (KHTML, like Gecko) MovieMagicBudgeting\/0.1.7 Chrome\/76.0.3809.131 Electron\/6.0.4 Safari\/537.36"""
val fixture = """
{
"ua":"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit\/537.36 (KHTML, like Gecko) MovieMagicBudgeting\/0.1.7 Chrome\/76.0.3809.131 Electron\/6.0.4 Safari\/537.36",
"ua":"$ua",
"type":"browser",
"brand":"Apple",
"name":"Mac",
Expand Down Expand Up @@ -72,18 +65,18 @@ class UserStackAPITest {
}
""".trimIndent()

val browser = UserStackAPI.parseBrowserMeta(fixture)
Assert.assertNotNull(browser)
Assert.assertTrue(browser.isNotEmpty())
Assert.assertEquals("Electron App", browser["browser"])
Assert.assertEquals("macOS 10.14 Mojave", browser["os"])
val browserMeta = UserStackAPI().parseBrowserMeta(GSON.fromJson(fixture, JsonObject::class.java), ua)
Assert.assertNotNull(browserMeta)
Assert.assertEquals("Electron App", browserMeta.browser())
Assert.assertEquals("macOS 10.14 Mojave", browserMeta.os.name)
}

@Test
fun parseBrowserMeta_chrome() {
val ua = """Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/78.0.3904.87 Safari\/537.36"""
val fixture = """
{
"ua":"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/78.0.3904.87 Safari\/537.36",
"ua":"$ua",
"type":"browser",
"brand":"Apple",
"name":"Mac",
Expand Down Expand Up @@ -120,10 +113,9 @@ class UserStackAPITest {
}
""".trimIndent()

val browser = UserStackAPI.parseBrowserMeta(fixture)
Assert.assertNotNull(browser)
Assert.assertTrue(browser.isNotEmpty())
Assert.assertEquals("Chrome 78.0.3904.87", browser["browser"])
Assert.assertEquals("macOS 10.14 Mojave", browser["os"])
val browserMeta = UserStackAPI().parseBrowserMeta(GSON.fromJson(fixture, JsonObject::class.java), ua)
Assert.assertNotNull(browserMeta)
Assert.assertEquals("Chrome 78.0.3904.87", browserMeta.browser())
Assert.assertEquals("macOS 10.14 Mojave", browserMeta.os.name)
}
}
Binary file modified template/nexial-macro.xlsx
Binary file not shown.
Binary file modified template/nexial-script.xlsx
Binary file not shown.

0 comments on commit 8b73871

Please sign in to comment.