Skip to content

Commit

Permalink
DuckPlayer mobile - overlay support
Browse files Browse the repository at this point in the history
  • Loading branch information
Shane Osbourne committed Sep 9, 2024
1 parent 43db011 commit 0af7c82
Show file tree
Hide file tree
Showing 23 changed files with 1,032 additions and 50 deletions.
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ docs/
/lib
Sources/ContentScopeScripts/dist/
integration-test/extension/contentScope.js
integration-test/pages/build
integration-test/test-pages/duckplayer/scripts/dist
packages/special-pages/pages/**/public
script-overload-snapshots/
packages/special-pages/playwright-report/
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ node_modules/
.build/
build/
docs/
/screens
test-results/
playwright-report/
Sources/ContentScopeScripts/dist/
test-results

# Local Netlify folder
.netlify
109 changes: 109 additions & 0 deletions integration-test/playwright/duckplayer-mobile.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { test } from '@playwright/test'
import { DuckplayerOverlays } from './page-objects/duckplayer-overlays.js'

test.describe('Video Player overlays', () => {
test('Selecting \'watch here\' on mobile', async ({ page }, workerInfo) => {
const overlays = DuckplayerOverlays.create(page, workerInfo)

// Given overlays feature is enabled
await overlays.withRemoteConfig()

// And my setting is 'always ask'
await overlays.userSettingIs('always ask')
await overlays.gotoPlayerPage()

// watch here = overlays removed
await overlays.mobile.choosesWatchHere()
await overlays.mobile.overlayIsRemoved()
await overlays.pixels.sendsPixels([
{ pixelName: 'overlay', params: {} },
{ pixelName: 'play.do_not_use', params: { remember: '0' } }
])
})
test('Selecting \'watch here\' on mobile + remember', async ({ page }, workerInfo) => {
const overlays = DuckplayerOverlays.create(page, workerInfo)

// Given overlays feature is enabled
await overlays.withRemoteConfig()

// And my setting is 'always ask'
await overlays.userSettingIs('always ask')
await overlays.gotoPlayerPage()

// watch here = overlays removed
await overlays.mobile.selectsRemember()
await overlays.mobile.choosesWatchHere()
await overlays.mobile.overlayIsRemoved()
await overlays.pixels.sendsPixels([
{ pixelName: 'overlay', params: {} },
{ pixelName: 'play.do_not_use', params: { remember: '1' } }
])
await overlays.userSettingWasUpdatedTo('disabled')
})
test('Selecting \'watch in duckplayer\' on mobile', async ({ page }, workerInfo) => {
const overlays = DuckplayerOverlays.create(page, workerInfo)

// Given overlays feature is enabled
await overlays.withRemoteConfig()

// And my setting is 'always ask'
await overlays.userSettingIs('always ask')
await overlays.gotoPlayerPage()

await overlays.mobile.choosesDuckPlayer()
await overlays.pixels.sendsPixels([
{ pixelName: 'overlay', params: {} },
{ pixelName: 'play.use', params: { remember: '0' } }
])
await overlays.userSettingWasUpdatedTo('always ask')
})
test('Selecting \'watch in duckplayer\' on mobile + remember', async ({ page }, workerInfo) => {
const overlays = DuckplayerOverlays.create(page, workerInfo)

// Given overlays feature is enabled
await overlays.withRemoteConfig()

// And my setting is 'always ask'
await overlays.userSettingIs('always ask')
await overlays.gotoPlayerPage()

await overlays.mobile.selectsRemember()
await overlays.mobile.choosesDuckPlayer()
await overlays.pixels.sendsPixels([
{ pixelName: 'overlay', params: {} },
{ pixelName: 'play.use', params: { remember: '1' } }
])
await overlays.userSettingWasUpdatedTo('enabled')
})
test('opens info', async ({ page }, workerInfo) => {
const overlays = DuckplayerOverlays.create(page, workerInfo)

// Given overlays feature is enabled
await overlays.withRemoteConfig()

// And my setting is 'always ask'
await overlays.userSettingIs('always ask')
await overlays.gotoPlayerPage()
await overlays.mobile.opensInfo()
})
})

/**
* Use this test in `--headed` mode to cycle through every language
*/
test.describe.skip('Translated Overlays', () => {
const items = ['bg', 'cs', 'da', 'de', 'el', 'en', 'es', 'et', 'fi', 'fr', 'hr', 'hu', 'it', 'lt', 'lv', 'nb', 'nl', 'pl', 'pt', 'ro', 'ru', 'sk', 'sl', 'sv', 'tr']
// const items = ['en']
for (const locale of items) {
test(`testing UI ${locale}`, async ({ page }, workerInfo) => {
// console.log(workerInfo.project.use.viewport.height)
// console.log(workerInfo.project.use.viewport.width)
const overlays = DuckplayerOverlays.create(page, workerInfo)
await overlays.withRemoteConfig({ locale })
await overlays.userSettingIs('always ask')
await overlays.gotoPlayerPage()
await page.locator('ddg-video-overlay-mobile').nth(0).waitFor()
await page.locator('.html5-video-player').screenshot({ path: `screens/se-2/${locale}.png` })
})
}
})
105 changes: 88 additions & 17 deletions integration-test/playwright/page-objects/duckplayer-overlays.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { readFileSync } from 'fs'
import {
mockAndroidMessaging,
mockResponses, mockWebkitMessaging,
mockWindowsMessaging,
readOutgoingMessages, simulateSubscriptionMessage, waitForCallCount, wrapWebkitScripts,
Expand Down Expand Up @@ -68,6 +69,8 @@ export class DuckplayerOverlays {
playerPage = '/duckplayer/pages/player.html'
videoAltSelectors = '/duckplayer/pages/video-alt-selectors.html'
serpProxyPage = '/duckplayer/pages/serp-proxy.html'
mobile = new DuckplayerOverlaysMobile(this)
pixels = new DuckplayerOverlayPixels(this)
/**
* @param {import("@playwright/test").Page} page
* @param {import("../type-helpers.mjs").Build} build
Expand Down Expand Up @@ -110,11 +113,6 @@ export class DuckplayerOverlays {
// await this.dismissCookies()
}

async gotoYoutubeSearchPage () {
await this.page.goto('https://www.youtube.com/results?search_query=taylor+swift')
// await this.dismissCookies()
}

async gotoYoutubeSearchPageForMovie () {
await this.page.goto('https://www.youtube.com/results?search_query=snatch')
// await this.dismissCookies()
Expand Down Expand Up @@ -241,18 +239,22 @@ export class DuckplayerOverlays {
/**
* @param {object} [params]
* @param {configFiles[number]} [params.json="overlays"] - default is settings for localhost
* @param {string} [params.locale] - optional locale
*/
async withRemoteConfig (params = {}) {
const { json = 'overlays.json' } = params
const {
json = 'overlays.json',
locale = 'en'
} = params

await this.setup({ config: loadConfig(json) })
await this.setup({ config: loadConfig(json), locale })
}

async serpProxyEnabled () {
const config = loadConfig('overlays.json')
const domains = config.features.duckPlayer.settings.domains[0].patchSettings
config.features.duckPlayer.settings.domains[0].patchSettings = domains.filter(x => x.path === '/overlays/serpProxy/state')
await this.setup({ config })
await this.setup({ config, locale: 'en' })
}

async videoOverlayDoesntShow () {
Expand All @@ -267,7 +269,8 @@ export class DuckplayerOverlays {
await this.page.addInitScript(mockResponses, {
responses: {
initialSetup: {
userValues: userValues[setting]
userValues: userValues[setting],
ui: {}
}
}
})
Expand All @@ -280,7 +283,8 @@ export class DuckplayerOverlays {
*/
async initialSetupIs (userValueSetting, uiSetting) {
const initialSetupResponse = {
userValues: userValues[userValueSetting]
userValues: userValues[userValueSetting],
ui: {}
}

if (uiSetting && uiSettings[uiSetting]) {
Expand Down Expand Up @@ -331,7 +335,7 @@ export class DuckplayerOverlays {
const config = loadConfig('overlays.json')
// remove all domains from 'overlays', this disables the feature
config.features.duckPlayer.settings.domains = []
await this.setup({ config })
await this.setup({ config, locale: 'en' })
}

async hoverAThumbnail () {
Expand Down Expand Up @@ -468,10 +472,11 @@ export class DuckplayerOverlays {
*
* @param {object} params
* @param {Record<string, any>} params.config
* @param {string} params.locale
* @return {Promise<void>}
*/
async setup (params) {
const { config } = params
const { config, locale } = params

await this.build.switch({
windows: async () => {
Expand All @@ -483,27 +488,38 @@ export class DuckplayerOverlays {
},
'apple-isolated': async () => {
// noop
},
android: async () => {
// noop
}
})

// read the built file from disk and do replacements
const wrapFn = this.build.switch({
'apple-isolated': () => wrapWebkitScripts,
windows: () => wrapWindowsScripts
windows: () => wrapWindowsScripts,
android: () => wrapWebkitScripts
})

const injectedJS = wrapFn(this.build.artifact, {
$CONTENT_SCOPE$: config,
$USER_UNPROTECTED_DOMAINS$: [],
$USER_PREFERENCES$: {
platform: { name: this.platform.name },
debug: true
debug: true,

// additional android keys
messageCallback: 'messageCallback',
messageSecret: 'duckduckgo-android-messaging-secret',
javascriptInterface: this.messagingContext,
locale
}
})

const mockMessaging = this.build.switch({
windows: () => mockWindowsMessaging,
'apple-isolated': () => mockWebkitMessaging
'apple-isolated': () => mockWebkitMessaging,
android: () => mockAndroidMessaging
})

await this.page.addInitScript(mockMessaging, {
Expand All @@ -517,7 +533,8 @@ export class DuckplayerOverlays {
userValues: {
privatePlayerMode: { alwaysAsk: {} },
overlayInteracted: false
}
},
ui: {}
},
getUserValues: {
privatePlayerMode: { alwaysAsk: {} },
Expand All @@ -537,7 +554,6 @@ export class DuckplayerOverlays {

/**
* @param {string} method
* @return {Promise<void>}
*/
async waitForMessage (method) {
await this.page.waitForFunction(waitForCallCount, {
Expand Down Expand Up @@ -642,6 +658,61 @@ export class DuckplayerOverlays {
}
}

class DuckplayerOverlaysMobile {
/**
* @param {DuckplayerOverlays} overlays
*/
constructor (overlays) {
this.overlays = overlays
}

async choosesWatchHere () {
const { page } = this.overlays
await page.getByRole('button', { name: 'No Thanks' }).click()
}

async choosesDuckPlayer () {
const { page } = this.overlays
await page.getByRole('link', { name: 'Turn On Duck Player' }).click()
}

async selectsRemember () {
const { page } = this.overlays
await page.getByRole('switch').click()
}

async overlayIsRemoved () {
const { page } = this.overlays
expect(await page.locator('ddg-video-overlay-mobile').count()).toBe(0)

Check failure on line 686 in integration-test/playwright/page-objects/duckplayer-overlays.js

View workflow job for this annotation

GitHub Actions / integration

[ios] › integration-test/playwright/duckplayer-mobile.spec.js:23:5 › Video Player overlays › Selecting 'watch here' on mobile + remember

1) [ios] › integration-test/playwright/duckplayer-mobile.spec.js:23:5 › Video Player overlays › Selecting 'watch here' on mobile + remember Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 at integration-test/playwright/page-objects/duckplayer-overlays.js:686 684 | async overlayIsRemoved () { 685 | const { page } = this.overlays > 686 | expect(await page.locator('ddg-video-overlay-mobile').count()).toBe(0) | ^ 687 | } 688 | 689 | async opensInfo () { at DuckplayerOverlaysMobile.overlayIsRemoved (/home/runner/work/content-scope-scripts/content-scope-scripts/integration-test/playwright/page-objects/duckplayer-overlays.js:686:72) at /home/runner/work/content-scope-scripts/content-scope-scripts/integration-test/playwright/duckplayer-mobile.spec.js:36:9
}

async opensInfo () {
const { page } = this.overlays
await page.getByLabel('Open Information Modal').click()
const messages = await this.overlays.waitForMessage('openInfo')
expect(messages).toHaveLength(1)
}
}

class DuckplayerOverlayPixels {
/**
* @param {DuckplayerOverlays} overlays
*/
constructor (overlays) {
this.overlays = overlays
}

/**
* @param {{pixelName: string, params: Record<string, any>}[]} pixels
* @return {Promise<void>}
*/
async sendsPixels (pixels) {
const messages = await this.overlays.waitForMessage('sendDuckPlayerPixel')
const params = messages.map(x => x.payload.params)
expect(params).toMatchObject(pixels)
}
}

/**
* @param {configFiles[number]} name
* @return {Record<string, any>}
Expand Down
1 change: 1 addition & 0 deletions integration-test/playwright/type-helpers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export class Build {
get artifact () {
const path = this.switch({
windows: () => 'build/windows/contentScope.js',
android: () => 'build/android/contentScope.js',
'apple': () => './Sources/ContentScopeScripts/dist/contentScope.js',
'apple-isolated': () => './Sources/ContentScopeScripts/dist/contentScopeIsolated.js'
})
Expand Down
Loading

0 comments on commit 0af7c82

Please sign in to comment.