Skip to content

Commit 0eb8e9e

Browse files
authored
Add CTA experiments (#675)
* Add new retention experiment to help up determine effectiveness of CTAs * Turn off old concept test experiment * Update browser pixel to only fire when visible * Add new pixels to fill engagement gaps in CTAs
1 parent 8c9d5ee commit 0eb8e9e

File tree

10 files changed

+165
-111
lines changed

10 files changed

+165
-111
lines changed

app/src/androidTest/java/com/duckduckgo/app/cta/ui/CtaViewModelTest.kt

Lines changed: 58 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import com.duckduckgo.app.privacy.store.PrivacySettingsStore
3737
import com.duckduckgo.app.settings.db.SettingsDataStore
3838
import com.duckduckgo.app.statistics.Variant
3939
import com.duckduckgo.app.statistics.VariantManager
40+
import com.duckduckgo.app.statistics.VariantManager.VariantFeature.ConceptTest
41+
import com.duckduckgo.app.statistics.VariantManager.VariantFeature.SuppressWidgetCta
4042
import com.duckduckgo.app.statistics.pixels.Pixel
4143
import com.duckduckgo.app.statistics.pixels.Pixel.PixelName.*
4244
import com.duckduckgo.app.survey.db.SurveyDao
@@ -45,12 +47,7 @@ import com.duckduckgo.app.survey.model.Survey.Status.SCHEDULED
4547
import com.duckduckgo.app.trackerdetection.model.Entity
4648
import com.duckduckgo.app.trackerdetection.model.TrackingEvent
4749
import com.duckduckgo.app.widget.ui.WidgetCapabilities
48-
import com.nhaarman.mockitokotlin2.any
49-
import com.nhaarman.mockitokotlin2.eq
50-
import com.nhaarman.mockitokotlin2.mock
51-
import com.nhaarman.mockitokotlin2.never
52-
import com.nhaarman.mockitokotlin2.verify
53-
import com.nhaarman.mockitokotlin2.whenever
50+
import com.nhaarman.mockitokotlin2.*
5451
import kotlinx.coroutines.ExperimentalCoroutinesApi
5552
import kotlinx.coroutines.test.runBlockingTest
5653
import org.junit.After
@@ -219,24 +216,22 @@ class CtaViewModelTest {
219216
}
220217

221218
@Test
222-
fun whenRefreshCtaOnHomeTabThenReturnDaxIntroCta() = runBlockingTest {
223-
setConceptTestVariant()
224-
219+
fun whenRefreshCtaOnHomeTabAndConceptTestFeatureActiveThenReturnDaxIntroCta() = runBlockingTest {
220+
setConceptTestFeature()
225221
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
226222
assertTrue(value is DaxBubbleCta.DaxIntroCta)
227223
}
228224

229225
@Test
230-
fun whenRefreshCtaWhileBrowsingAndSiteIsNullThenReturnNull() = runBlockingTest {
231-
setConceptTestVariant()
232-
226+
fun whenRefreshCtaWhileBrowsingAndConceptTestFeatureActiveAndSiteIsNullThenReturnNull() = runBlockingTest {
227+
setConceptTestFeature()
233228
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true, site = null)
234229
assertNull(value)
235230
}
236231

237232
@Test
238-
fun whenRefreshCtaWhileBrowsingAndHideTipsIsTrueThenReturnNull() = runBlockingTest {
239-
setConceptTestVariant()
233+
fun whenRefreshCtaWhileBrowsingAndConceptTestFeatureActiveAndHideTipsIsTrueThenReturnNull() = runBlockingTest {
234+
setConceptTestFeature()
240235
whenever(mockSettingsDataStore.hideTips).thenReturn(true)
241236
val site = site(url = "http://www.facebook.com", entity = TestEntity("Facebook", "Facebook", 9.0))
242237

@@ -245,8 +240,8 @@ class CtaViewModelTest {
245240
}
246241

247242
@Test
248-
fun whenRefreshCtaWhileBrowsingAndPrivacyOffThenReturnNull() = runBlockingTest {
249-
setConceptTestVariant()
243+
fun whenRefreshCtaWhileBrowsingAndConceptTestFeatureActiveAndPrivacyOffThenReturnNull() = runBlockingTest {
244+
setConceptTestFeature()
250245
whenever(mockPrivacySettingsStore.privacyOn).thenReturn(false)
251246
val site = site(url = "http://www.facebook.com", entity = TestEntity("Facebook", "Facebook", 9.0))
252247

@@ -255,17 +250,16 @@ class CtaViewModelTest {
255250
}
256251

257252
@Test
258-
fun whenRefreshCtaWhileBrowsingAndVariantIsNotConceptTestThenReturnNull() = runBlockingTest {
259-
setDefaultVariant()
253+
fun whenRefreshCtaWhileBrowsingAndNoFeaturesActiveThenReturnNull() = runBlockingTest {
254+
setNoFeatures()
260255
val site = site(url = "http://www.facebook.com", entity = TestEntity("Facebook", "Facebook", 9.0))
261-
262256
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true, site = site)
263257
assertNull(value)
264258
}
265259

266260
@Test
267-
fun whenRefreshCtaOnHomeTabThenReturnDaxEndCta() = runBlockingTest {
268-
setConceptTestVariant()
261+
fun whenRefreshCtaOnHomeTabAndConceptTestFeatureActiveThenReturnDaxEndCta() = runBlockingTest {
262+
setConceptTestFeature()
269263
whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(true)
270264
whenever(mockDismissedCtaDao.exists(CtaId.DAX_DIALOG_SERP)).thenReturn(true)
271265

@@ -274,49 +268,48 @@ class CtaViewModelTest {
274268
}
275269

276270
@Test
277-
fun whenRefreshCtaOnHomeTabAndHideTipsIsTrueThenReturnNull() = runBlockingTest {
278-
setConceptTestVariant()
271+
fun whenRefreshCtaOnHomeTabAndConceptTestFeatureActiveAndHideTipsIsTrueThenReturnNull() = runBlockingTest {
272+
setConceptTestFeature()
279273
whenever(mockSettingsDataStore.hideTips).thenReturn(true)
280274

281-
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true)
275+
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
282276
assertNull(value)
283277
}
284278

285279
@Test
286-
fun whenRefreshCtaOnHomeTabAndVarianIstNotConceptTestThenReturnNull() = runBlockingTest {
287-
setDefaultVariant()
288-
289-
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true)
280+
fun whenRefreshCtaOnHomeTabAndNoFeaturesActiveThenReturnNull() = runBlockingTest {
281+
setNoFeatures()
282+
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
290283
assertNull(value)
291284
}
292285

293286
@Test
294-
fun whenRefreshCtaOnHomeTabAndVariantIsConceptTestThenDoNotShowWidgetAutoCta() = runBlockingTest {
295-
setConceptTestVariant()
287+
fun whenRefreshCtaOnHomeTabAndSuppressWidgetCtaFeatureThenDoNotShowWidgetAutoCta() = runBlockingTest {
288+
setSuppressWidgetCtaFeature()
296289
whenever(mockSettingsDataStore.hideTips).thenReturn(true)
297290
whenever(mockWidgetCapabilities.supportsStandardWidgetAdd).thenReturn(true)
298291
whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(true)
299292
whenever(mockWidgetCapabilities.hasInstalledWidgets).thenReturn(false)
300293

301-
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true)
294+
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
302295
assertTrue(value !is HomePanelCta.AddWidgetAuto)
303296
}
304297

305298
@Test
306-
fun whenRefreshCtaOnHomeTabAndVariantIsConceptTestThenDoNotShowWidgetInstructionsCta() = runBlockingTest {
307-
setConceptTestVariant()
299+
fun whenRefreshCtaOnHomeTabAndSuppressWidgetCtaFeatureActiveThenDoNotShowWidgetInstructionsCta() = runBlockingTest {
300+
setSuppressWidgetCtaFeature()
308301
whenever(mockSettingsDataStore.hideTips).thenReturn(true)
309302
whenever(mockWidgetCapabilities.supportsStandardWidgetAdd).thenReturn(true)
310303
whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(false)
311304
whenever(mockWidgetCapabilities.hasInstalledWidgets).thenReturn(false)
312305

313-
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true)
306+
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
314307
assertTrue(value !is HomePanelCta.AddWidgetInstructions)
315308
}
316309

317310
@Test
318311
fun whenRefreshCtaOnHomeTabAndHideTipsIsTrueThenReturnWidgetAutoCta() = runBlockingTest {
319-
setDefaultVariant()
312+
setNoFeatures()
320313
whenever(mockSettingsDataStore.hideTips).thenReturn(true)
321314
whenever(mockWidgetCapabilities.supportsStandardWidgetAdd).thenReturn(true)
322315
whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(true)
@@ -328,7 +321,7 @@ class CtaViewModelTest {
328321

329322
@Test
330323
fun whenRefreshCtaOnHomeTabAndHideTipsIsTrueThenReturnWidgetInstructionsCta() = runBlockingTest {
331-
setDefaultVariant()
324+
setNoFeatures()
332325
whenever(mockSettingsDataStore.hideTips).thenReturn(true)
333326
whenever(mockWidgetCapabilities.supportsStandardWidgetAdd).thenReturn(true)
334327
whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(false)
@@ -339,41 +332,41 @@ class CtaViewModelTest {
339332
}
340333

341334
@Test
342-
fun whenRefreshCtaOnHomeTabVariantIsNoCtaReturnNullWhenTryngToShowWidgetCta() = runBlockingTest {
343-
setNoCtaVariant()
335+
fun whenRefreshCtaOnHomeTabAndSuppressWidgetCtaFeatureActiveThenReturnNullWhenTryngToShowWidgetCta() = runBlockingTest {
336+
setSuppressWidgetCtaFeature()
344337
whenever(mockSettingsDataStore.hideTips).thenReturn(true)
345338
whenever(mockWidgetCapabilities.supportsStandardWidgetAdd).thenReturn(true)
346339
whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(true)
347340
whenever(mockWidgetCapabilities.hasInstalledWidgets).thenReturn(false)
348341

349-
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true)
342+
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
350343
assertNull(value)
351344
}
352345

353346
@Test
354-
fun whenRefreshCtaOnHomeTabVariantIsNoCtaReturnNullWhenTryngToShowWidgetInstructionsCta() = runBlockingTest {
355-
setNoCtaVariant()
347+
fun whenRefreshCtaOnHomeTabAndSuppressWidgetCtaFeatureActiveThenReturnNullWhenTryngToShowWidgetInstructionsCta() = runBlockingTest {
348+
setSuppressWidgetCtaFeature()
356349
whenever(mockSettingsDataStore.hideTips).thenReturn(true)
357350
whenever(mockWidgetCapabilities.supportsStandardWidgetAdd).thenReturn(true)
358351
whenever(mockWidgetCapabilities.supportsAutomaticWidgetAdd).thenReturn(false)
359352
whenever(mockWidgetCapabilities.hasInstalledWidgets).thenReturn(false)
360353

361-
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true)
354+
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
362355
assertNull(value)
363356
}
364357

365358
@Test
366-
fun whenRefreshCtaWhileBrowsingThenReturnNetworkCta() = runBlockingTest {
367-
setConceptTestVariant()
359+
fun whenRefreshCtaWhileBrowsingAndConceptTestFeatureActiveThenReturnNetworkCta() = runBlockingTest {
360+
setConceptTestFeature()
368361
val site = site(url = "http://www.facebook.com", entity = TestEntity("Facebook", "Facebook", 9.0))
369362
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true, site = site)
370363

371364
assertTrue(value is DaxDialogCta.DaxMainNetworkCta)
372365
}
373366

374367
@Test
375-
fun whenRefreshCtaWhileBrowsingThenReturnTrackersBlockedCta() = runBlockingTest {
376-
setConceptTestVariant()
368+
fun whenRefreshCtaWhileBrowsingAndConceptTestFeatureActiveThenReturnTrackersBlockedCta() = runBlockingTest {
369+
setConceptTestFeature()
377370
val trackingEvent = TrackingEvent("test.com", "test.com", null, TestEntity("test", "test", 9.0), true)
378371
val site = site(url = "http://www.cnn.com", trackerCount = 1, events = listOf(trackingEvent))
379372
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true, site = site)
@@ -382,70 +375,64 @@ class CtaViewModelTest {
382375
}
383376

384377
@Test
385-
fun whenRefreshCtaWhileBrowsingButNoTrackersInformationThenReturnNoSerpCta() = runBlockingTest {
386-
setConceptTestVariant()
378+
fun whenRefreshCtaWhileBrowsingAndConceptTestFeatureActiveAndNoTrackersInformationThenReturnNoSerpCta() = runBlockingTest {
379+
setConceptTestFeature()
387380
val site = site(url = "http://www.cnn.com", trackerCount = 1)
388381
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true, site = site)
389382

390383
assertTrue(value is DaxDialogCta.DaxNoSerpCta)
391384
}
392385

393386
@Test
394-
fun whenRefreshCtaWhileBrowsingThenReturnSerpCta() = runBlockingTest {
395-
setConceptTestVariant()
387+
fun whenRefreshCtaOnWhileBrowsingAndConceptTestFeatureActiveThenReturnSerpCta() = runBlockingTest {
388+
setConceptTestFeature()
396389
val site = site(url = "http://www.duckduckgo.com")
397390
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true, site = site)
398391

399392
assertTrue(value is DaxDialogCta.DaxSerpCta)
400393
}
401394

402395
@Test
403-
fun whenRefreshCtaWhileBrowsingThenReturnNoSerpCta() = runBlockingTest {
404-
setConceptTestVariant()
396+
fun whenRefreshCtaWhileBrowsingAndConceptFeatureActiveTestThenReturnNoSerpCta() = runBlockingTest {
397+
setConceptTestFeature()
405398
val site = site(url = "http://www.wikipedia.com")
406399
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true, site = site)
407400

408401
assertTrue(value is DaxDialogCta.DaxNoSerpCta)
409402
}
410403

411404
@Test
412-
fun whenRefreshCtaOnHomeTabThenValueReturnedIsNotDaxDialogCtaType() = runBlockingTest {
413-
setConceptTestVariant()
405+
fun whenRefreshCtaOnHomeTabAndConceptTestFeatureActiveThenValueReturnedIsNotDaxDialogCtaType() = runBlockingTest {
406+
setConceptTestFeature()
414407
val site = site(url = "http://www.wikipedia.com")
415408
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false, site = site)
416409

417410
assertTrue(value !is DaxDialogCta)
418411
}
419412

420413
@Test
421-
fun whenRefreshCtaOnHomeTabEndCtaNotShownIfIntroCtaWasNotPreviouslyShown() = runBlockingTest {
422-
setConceptTestVariant()
414+
fun whenRefreshCtaOnHomeTabAndConceptTestFeatureActiveAndIntroCtaWasNotPreviouslyShownThenEndCtaNotShown() = runBlockingTest {
415+
setConceptTestFeature()
423416
whenever(mockDismissedCtaDao.exists(CtaId.DAX_INTRO)).thenReturn(false)
424417
whenever(mockDismissedCtaDao.exists(CtaId.DAX_DIALOG_SERP)).thenReturn(true)
425418

426-
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = true)
419+
val value = testee.refreshCta(coroutineRule.testDispatcher, isBrowserShowing = false)
427420
assertTrue(value !is DaxBubbleCta.DaxEndCta)
428421
}
429422

430-
private fun setConceptTestVariant() {
431-
whenever(mockVariantManager.getVariant()).thenReturn(
432-
Variant(
433-
"test",
434-
features = listOf(VariantManager.VariantFeature.ConceptTest),
435-
filterBy = { true })
436-
)
423+
private fun setNoFeatures() {
424+
whenever(mockVariantManager.getVariant()).thenReturn(Variant("test", features = emptyList(), filterBy = { true }))
437425
}
438426

439-
private fun setDefaultVariant() {
440-
whenever(mockVariantManager.getVariant()).thenReturn(Variant("test", features = emptyList(), filterBy = { true }))
427+
private fun setConceptTestFeature() {
428+
whenever(mockVariantManager.getVariant()).thenReturn(
429+
Variant("test", features = listOf(ConceptTest), filterBy = { true })
430+
)
441431
}
442432

443-
private fun setNoCtaVariant() {
433+
private fun setSuppressWidgetCtaFeature() {
444434
whenever(mockVariantManager.getVariant()).thenReturn(
445-
Variant(
446-
"test",
447-
features = listOf(VariantManager.VariantFeature.ExistingNoCta),
448-
filterBy = { true })
435+
Variant("test", features = listOf(SuppressWidgetCta), filterBy = { true })
449436
)
450437
}
451438

app/src/androidTest/java/com/duckduckgo/app/onboarding/ui/page/DefaultBrowserPageViewModelTest.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,16 @@ class DefaultBrowserPageViewModelTest {
7272
}
7373

7474
@Test
75-
fun whenInitializeThenSendPixel() {
76-
verify(mockPixel).fire(Pixel.PixelName.ONBOARDING_DEFAULT_BROWSER_VISUALIZED)
75+
fun whenPageBecomesVisibleThenPixelSent() {
76+
testee.pageBecameVisible()
77+
verify(mockPixel, times(1)).fire(Pixel.PixelName.ONBOARDING_DEFAULT_BROWSER_VISUALIZED)
78+
}
79+
80+
@Test
81+
fun whenPageBecomesVisibleSubsequentTimeThenAdditionalPixelNotSent() {
82+
testee.pageBecameVisible()
83+
testee.pageBecameVisible()
84+
verify(mockPixel, times(1)).fire(Pixel.PixelName.ONBOARDING_DEFAULT_BROWSER_VISUALIZED)
7785
}
7886

7987
@Test

0 commit comments

Comments
 (0)