Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new WebView Interface #700

Open
wants to merge 15 commits into
base: release/6.1.0
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
/*
* Copyright (c) 2015-present Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/

package com.snowplowanalytics.snowplow.tracker

import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.snowplowanalytics.core.constants.Parameters
import com.snowplowanalytics.core.constants.TrackerConstants
import com.snowplowanalytics.core.emitter.Executor
import com.snowplowanalytics.core.tracker.TrackerWebViewInterfaceV2
import com.snowplowanalytics.snowplow.Snowplow.createTracker
import com.snowplowanalytics.snowplow.Snowplow.removeAllTrackers
import com.snowplowanalytics.snowplow.configuration.NetworkConfiguration
import com.snowplowanalytics.snowplow.configuration.PluginConfiguration
import com.snowplowanalytics.snowplow.configuration.TrackerConfiguration
import com.snowplowanalytics.snowplow.controller.TrackerController
import com.snowplowanalytics.snowplow.network.HttpMethod
import org.json.JSONException
import org.json.JSONObject
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class TrackerWebViewInterfaceV2Test {
private var webInterface: TrackerWebViewInterfaceV2? = null
private var networkConnection = MockNetworkConnection(HttpMethod.GET, 200)
mscwilson marked this conversation as resolved.
Show resolved Hide resolved
private var tracker: TrackerController? = null

@Before
fun setUp() {
webInterface = TrackerWebViewInterfaceV2()
tracker = createTracker()
}

@After
fun tearDown() {
tracker?.pause()
tracker = null
removeAllTrackers()
Executor.shutdown()
}

@Test
@Throws(JSONException::class, InterruptedException::class)
fun tracksEventWithAllOptions() {
val data = "{\"schema\":\"iglu:etc\",\"data\":{\"key\":\"val\"}}"

webInterface!!.trackWebViewEvent(
eventName = "ue",
trackerVersion = "webview",
useragent = "Chrome",
selfDescribingEventData = data,
pageUrl = "http://snowplow.com",
pageTitle = "Snowplow",
referrer = "http://google.com",
pingXOffsetMin = 10,
pingXOffsetMax = 20,
pingYOffsetMin = 30,
pingYOffsetMax = 40,
category = "cat",
action = "act",
property = "prop",
label = "lbl",
value = 10.0
)

Thread.sleep(200)
waitForEvents(networkConnection, 1)

assertEquals(1, networkConnection.countRequests())

val request = networkConnection.allRequests[0]
val payload = request.payload.map

assertEquals("ue", payload[Parameters.EVENT])
matus-tomlein marked this conversation as resolved.
Show resolved Hide resolved
assertEquals("webview", payload[Parameters.TRACKER_VERSION])
assertEquals("Chrome", payload[Parameters.USERAGENT])
assertEquals("http://snowplow.com", payload[Parameters.PAGE_URL])
assertEquals("Snowplow", payload[Parameters.PAGE_TITLE])
assertEquals("http://google.com", payload[Parameters.PAGE_REFR])
assertEquals("10", payload[Parameters.PING_XOFFSET_MIN])
assertEquals("20", payload[Parameters.PING_XOFFSET_MAX])
assertEquals("30", payload[Parameters.PING_YOFFSET_MIN])
assertEquals("40", payload[Parameters.PING_YOFFSET_MAX])
assertEquals("cat", payload[Parameters.SE_CATEGORY])
assertEquals("act", payload[Parameters.SE_ACTION])
assertEquals("prop", payload[Parameters.SE_PROPERTY])
assertEquals("lbl", payload[Parameters.SE_LABEL])
assertEquals("10.0", payload[Parameters.SE_VALUE])

assertTrue(payload.containsKey(Parameters.UNSTRUCTURED))
val selfDescJson = JSONObject(payload[Parameters.UNSTRUCTURED] as String)
assertEquals(TrackerConstants.SCHEMA_UNSTRUCT_EVENT, selfDescJson.getString("schema"))
assertEquals(data, selfDescJson.getString("data"))
}

@Test
@Throws(JSONException::class, InterruptedException::class)
fun tracksEventWithCorrectTracker() {
// create the second tracker
val networkConnection2 = MockNetworkConnection(HttpMethod.GET, 200)
createTracker(
context,
namespace = "ns2",
NetworkConfiguration(networkConnection2),
TrackerConfiguration("appId")
)
Thread.sleep(200)

// track an event using the second tracker
webInterface!!.trackWebViewEvent(
eventName = "pv",
trackerVersion = "webview",
useragent = "Chrome",
pageUrl = "http://snowplow.com",
trackers = arrayOf("ns2")
)
Thread.sleep(200)
waitForEvents(networkConnection2, 1)

assertEquals(0, networkConnection.countRequests())
assertEquals(1, networkConnection2.countRequests())

assertEquals("pv", networkConnection2.allRequests[0].payload.map[Parameters.EVENT])

// tracks using default tracker if not specified
webInterface!!.trackWebViewEvent(
eventName = "pp",
trackerVersion = "webview",
useragent = "Chrome",
pageUrl = "http://snowplow.com",
)
Thread.sleep(200)
waitForEvents(networkConnection, 1)

assertEquals(1, networkConnection.countRequests())
assertEquals(1, networkConnection2.countRequests())
}

@Test
@Throws(JSONException::class, InterruptedException::class)
fun tracksEventWithEntity() {
val entities = "[{\"schema\":\"iglu:com.example/etc\",\"data\":{\"key\":\"val\"}}]"
webInterface!!.trackWebViewEvent(
eventName = "pp",
trackerVersion = "webview",
useragent = "Chrome",
pageUrl = "http://snowplow.com",
entities = entities
)
Thread.sleep(200)
waitForEvents(networkConnection, 1)

assertEquals(1, networkConnection.countRequests())

val relevantEntities = ArrayList<JSONObject>()
val allEntities = JSONObject(networkConnection.allRequests[0].payload.map[Parameters.CONTEXT] as String)
.getJSONArray("data")
for (i in 0 until allEntities.length()) {
if (allEntities.getJSONObject(i).getString("schema") == "iglu:com.example/etc") {
relevantEntities.add(allEntities.getJSONObject(i).getJSONObject("data"))
}
}
assertEquals(1, relevantEntities.size)
assertEquals("val", relevantEntities[0].get("key") as? String)
}

@Test
@Throws(JSONException::class, InterruptedException::class)
fun addsEventNameAndSchemaForInspection() {
val trackedEvents: MutableList<InspectableEvent> = mutableListOf()

val namespace = "ns" + Math.random().toString()
val networkConfig = NetworkConfiguration(MockNetworkConnection(HttpMethod.POST, 200))

val plugin = PluginConfiguration("plugin")
plugin.afterTrack { trackedEvents.add(it) }

createTracker(
context,
namespace,
networkConfig,
TrackerConfiguration("appId"),
plugin
)

val data = "{\"schema\":\"iglu:etc\",\"data\":{\"key\":\"val\"}}"
webInterface!!.trackWebViewEvent(
eventName = "se",
trackerVersion = "webview",
useragent = "Chrome",
selfDescribingEventData = data,
trackers = arrayOf(namespace)
)

Thread.sleep(200)
assertEquals(1, trackedEvents.size)
assertEquals("se", trackedEvents[0].name)
assertEquals("iglu:etc", trackedEvents[0].schema)
}

// --- PRIVATE
private val context: Context
get() = InstrumentationRegistry.getInstrumentation().targetContext

private fun createTracker(): TrackerController {
val trackerConfig = TrackerConfiguration("appId")
.installAutotracking(false)
.lifecycleAutotracking(false)
.platformContext(false)
.base64encoding(false)

return createTracker(
context,
"ns${Math.random()}",
NetworkConfiguration(networkConnection),
trackerConfig
)
}

@Throws(Exception::class)
fun waitForEvents(networkConnection: MockNetworkConnection, eventsExpected: Int) {
var i = 0
while (i < 10 && networkConnection.countRequests() == eventsExpected - 1) {
Thread.sleep(1000)
i++
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,11 @@ object Parameters {
const val DIAGNOSTIC_ERROR_STACK = "stackTrace"
const val DIAGNOSTIC_ERROR_CLASS_NAME = "className"
const val DIAGNOSTIC_ERROR_EXCEPTION_NAME = "exceptionName"

// Page Pings (for WebView tracking)
const val PING_XOFFSET_MIN = "pp_mix"
const val PING_XOFFSET_MAX = "pp_max"
const val PING_YOFFSET_MIN = "pp_miy"
const val PING_YOFFSET_MAX = "pp_may"
const val WEBVIEW_EVENT_DATA = "selfDescribingEventData"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2015-present Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
package com.snowplowanalytics.core.event

import com.snowplowanalytics.core.constants.Parameters
import com.snowplowanalytics.snowplow.event.AbstractEvent
import com.snowplowanalytics.snowplow.payload.SelfDescribingJson

/**
* Allows the tracking of JavaScript events from WebViews.
*/
class WebViewReader(
val eventName: String,
val trackerVersion: String,
val useragent: String,
val selfDescribingEventData: SelfDescribingJson? = null,
val pageUrl: String? = null,
val pageTitle: String? = null,
val referrer: String? = null,
val category: String? = null,
val action: String? = null,
val label: String? = null,
val property: String? = null,
val value: Double? = null,
val pingXOffsetMin: Int? = null,
val pingXOffsetMax: Int? = null,
val pingYOffsetMin: Int? = null,
val pingYOffsetMax: Int? = null
) : AbstractEvent() {

// Public methods
override val dataPayload: Map<String, Any?>
get() {
val payload = HashMap<String, Any?>()
payload[Parameters.EVENT] = eventName
payload[Parameters.TRACKER_VERSION] = trackerVersion
payload[Parameters.USERAGENT] = useragent

if (selfDescribingEventData != null) payload[Parameters.WEBVIEW_EVENT_DATA] = selfDescribingEventData
if (pageUrl != null) payload[Parameters.PAGE_URL] = pageUrl
if (pageTitle != null) payload[Parameters.PAGE_TITLE] = pageTitle
if (referrer != null) payload[Parameters.PAGE_REFR] = referrer
if (category != null) payload[Parameters.SE_CATEGORY] = category
if (action != null) payload[Parameters.SE_ACTION] = action
if (label != null) payload[Parameters.SE_LABEL] = label
if (property != null) payload[Parameters.SE_PROPERTY] = property
if (value != null) payload[Parameters.SE_VALUE] = value
if (pingXOffsetMin != null) payload[Parameters.PING_XOFFSET_MIN] = pingXOffsetMin
if (pingXOffsetMax != null) payload[Parameters.PING_XOFFSET_MAX] = pingXOffsetMax
if (pingYOffsetMin != null) payload[Parameters.PING_YOFFSET_MIN] = pingYOffsetMin
if (pingYOffsetMax != null) payload[Parameters.PING_YOFFSET_MAX] = pingYOffsetMax
return payload
}
}
Loading
Loading