diff --git a/injected/entry-points/integration.js b/injected/entry-points/integration.js index b3fb82c42d..7972a2d739 100644 --- a/injected/entry-points/integration.js +++ b/injected/entry-points/integration.js @@ -33,6 +33,7 @@ function generateConfig() { 'cookie', 'webCompat', 'apiManipulation', + 'duckPlayer', ], }, }; diff --git a/injected/integration-test/duckplayer-mobile-drawer.spec.js b/injected/integration-test/duckplayer-mobile-drawer.spec.js new file mode 100644 index 0000000000..dbdd7874fc --- /dev/null +++ b/injected/integration-test/duckplayer-mobile-drawer.spec.js @@ -0,0 +1,192 @@ +import { expect, test } from '@playwright/test'; +import { DuckplayerOverlays } from './page-objects/duckplayer-overlays.js'; + +test.describe('Duck Player - Drawer UI variant', () => { + test.describe('Video Player overlays', () => { + test("Selecting 'watch here' on mobile", async ({ page }, workerInfo) => { + const overlays = DuckplayerOverlays.create(page, workerInfo); + await overlays.reducedMotion(); + + // Given drawer overlay variant is set + await overlays.withRemoteConfig({ json: 'overlays-drawer.json' }); + + // 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); + await overlays.reducedMotion(); + + // Given drawer overlay variant is set + await overlays.withRemoteConfig({ json: 'overlays-drawer.json' }); + + // 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); + await overlays.reducedMotion(); + + // Given drawer overlay variant is set + await overlays.withRemoteConfig({ json: 'overlays-drawer.json' }); + + // 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); + await overlays.reducedMotion(); + + // Given drawer overlay variant is set + await overlays.withRemoteConfig({ json: 'overlays-drawer.json' }); + + // 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('Clicking on video thumbnail dismisses overlay', async ({ page }, workerInfo) => { + const overlays = DuckplayerOverlays.create(page, workerInfo); + await overlays.reducedMotion(); + + // Given drawer overlay variant is set + await overlays.withRemoteConfig({ json: 'overlays-drawer.json' }); + + // And my setting is 'always ask' + await overlays.userSettingIs('always ask'); + await overlays.gotoPlayerPage(); + + await overlays.mobile.clicksOnVideoThumbnail(); + await overlays.pixels.sendsPixels([ + { pixelName: 'overlay', params: {} }, + { pixelName: 'play.do_not_use.thumbnail', params: {} }, + ]); + await overlays.userSettingWasNotUpdated(); + }); + test('Clicking on drawer backdrop dismisses overlay', async ({ page }, workerInfo) => { + const overlays = DuckplayerOverlays.create(page, workerInfo); + await overlays.reducedMotion(); + + // Given drawer overlay variant is set + await overlays.withRemoteConfig({ json: 'overlays-drawer.json' }); + + // And my setting is 'always ask' + await overlays.userSettingIs('always ask'); + await overlays.gotoPlayerPage(); + + await overlays.mobile.clicksOnDrawerBackdrop(); + await overlays.pixels.sendsPixels([ + { pixelName: 'overlay', params: {} }, + { pixelName: 'play.do_not_use', params: {} }, + ]); + await overlays.userSettingWasNotUpdated(); + }); + test('opens info', async ({ page }, workerInfo) => { + const overlays = DuckplayerOverlays.create(page, workerInfo); + await overlays.reducedMotion(); + + // Given drawer overlay variant is set + await overlays.withRemoteConfig({ json: 'overlays-drawer.json' }); + + // 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({ json: 'overlays-drawer.json', locale }); + await overlays.userSettingIs('always ask'); + await overlays.gotoPlayerPage(); + await page.locator('ddg-video-drawer-mobile').nth(0).waitFor(); + await page.locator('.html5-video-player').screenshot({ path: `screens/se-2/${locale}.png` }); + }); + } + }); + + /** + * Use `npm run playwright-screenshots` to run this test only. + */ + test.describe('Overlay screenshot @screenshots', () => { + test("testing Overlay UI 'en'", async ({ page }, workerInfo) => { + const overlays = DuckplayerOverlays.create(page, workerInfo); + await overlays.withRemoteConfig({ json: 'overlays-drawer.json', locale: 'en' }); + await overlays.userSettingIs('always ask'); + await overlays.gotoPlayerPage(); + await page.locator('ddg-video-drawer-mobile').nth(0).waitFor(); + await expect(page.locator('ddg-video-drawer-mobile')).toHaveScreenshot('drawer.png', { maxDiffPixels: 20 }); + }); + }); +}); diff --git a/injected/integration-test/duckplayer-mobile-drawer.spec.js-snapshots/drawer-android-darwin.png b/injected/integration-test/duckplayer-mobile-drawer.spec.js-snapshots/drawer-android-darwin.png new file mode 100644 index 0000000000..da4b5f3729 Binary files /dev/null and b/injected/integration-test/duckplayer-mobile-drawer.spec.js-snapshots/drawer-android-darwin.png differ diff --git a/injected/integration-test/duckplayer-mobile-drawer.spec.js-snapshots/drawer-ios-darwin.png b/injected/integration-test/duckplayer-mobile-drawer.spec.js-snapshots/drawer-ios-darwin.png new file mode 100644 index 0000000000..e46b0bc218 Binary files /dev/null and b/injected/integration-test/duckplayer-mobile-drawer.spec.js-snapshots/drawer-ios-darwin.png differ diff --git a/injected/integration-test/page-objects/duckplayer-overlays.js b/injected/integration-test/page-objects/duckplayer-overlays.js index 7e91d54e4a..313445cbbc 100644 --- a/injected/integration-test/page-objects/duckplayer-overlays.js +++ b/injected/integration-test/page-objects/duckplayer-overlays.js @@ -38,6 +38,7 @@ const uiSettings = { const configFiles = /** @type {const} */ ([ 'overlays.json', 'overlays-live.json', + 'overlays-drawer.json', 'disabled.json', 'thumbnail-overlays-disabled.json', 'click-interceptions-disabled.json', @@ -90,6 +91,10 @@ export class DuckplayerOverlays { }); } + async reducedMotion() { + await this.page.emulateMedia({ reducedMotion: 'reduce' }); + } + /** * @param {object} params * @param {'default' | 'cookie_banner'} [params.variant] @@ -472,6 +477,17 @@ export class DuckplayerOverlays { ]); } + /** + * @return {Promise} + */ + async userSettingWasNotUpdated() { + const messages = await this.collector.outgoingMessages(); + // @ts-expect-error - Subscription is missing method property + const setUserValuesMessages = messages.filter((message) => message.payload?.method === 'setUserValues'); + + expect(setUserValuesMessages.length).toBe(0); + } + /** * Helper for creating an instance per platform * @param {import("@playwright/test").Page} page @@ -536,6 +552,16 @@ class DuckplayerOverlaysMobile { await page.getByRole('link', { name: 'Turn On Duck Player' }).click(); } + async clicksOnVideoThumbnail() { + const { page } = this.overlays; + await page.locator('ddg-video-thumbnail-overlay-mobile .bg').click({ force: true }); + } + + async clicksOnDrawerBackdrop() { + const { page } = this.overlays; + await page.locator('ddg-video-drawer-mobile .ddg-mobile-drawer-background').click({ position: { x: 10, y: 10 } }); + } + async selectsRemember() { const { page } = this.overlays; await page.getByRole('switch').click(); diff --git a/injected/integration-test/test-pages/duckplayer/config/overlays-drawer.json b/injected/integration-test/test-pages/duckplayer/config/overlays-drawer.json new file mode 100644 index 0000000000..56d234a446 --- /dev/null +++ b/injected/integration-test/test-pages/duckplayer/config/overlays-drawer.json @@ -0,0 +1,62 @@ +{ + "unprotectedTemporary": [], + "features": { + "duckPlayer": { + "state": "enabled", + "exceptions": [], + "settings": { + "overlays": { + "youtube": { + "state": "disabled", + "selectors": { + "thumbLink": "a[href^='/watch']", + "excludedRegions": [ + "#playlist", + "ytd-movie-renderer", + "ytd-grid-movie-renderer" + ], + "videoElement": "#player video", + "videoElementContainer": "#player .html5-video-player", + "hoverExcluded": [".an-overlay-causing-breakage"], + "clickExcluded": [".an-overlay-causing-breakage"], + "allowedEventTargets": [], + "drawerContainer": "body" + }, + "thumbnailOverlays": { + "state": "enabled" + }, + "clickInterception": { + "state": "enabled" + }, + "videoOverlays": { + "state": "enabled" + }, + "videoDrawer": { + "state": "enabled" + } + }, + "serpProxy": { + "state": "disabled" + } + }, + "domains": [ + { + "domain": "localhost", + "patchSettings": [ + { + "op": "replace", + "path": "/overlays/youtube/state", + "value": "enabled" + }, + { + "op": "replace", + "path": "/overlays/serpProxy/state", + "value": "enabled" + } + ] + } + ] + } + } + } +} diff --git a/injected/integration-test/test-pages/duckplayer/pages/player.html b/injected/integration-test/test-pages/duckplayer/pages/player.html index 2dfa7c3f58..e3321beaa0 100644 --- a/injected/integration-test/test-pages/duckplayer/pages/player.html +++ b/injected/integration-test/test-pages/duckplayer/pages/player.html @@ -154,13 +154,23 @@