Skip to content

Commit 687d4ad

Browse files
shakyShaneShane Osbourne
and
Shane Osbourne
authored
macos duckplayer (#459)
* Duck Player in isolated world, macos --------- Co-authored-by: Shane Osbourne <[email protected]>
1 parent e12550d commit 687d4ad

File tree

26 files changed

+1023
-275
lines changed

26 files changed

+1023
-275
lines changed

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ let package = Package(
2121
dependencies: [],
2222
resources: [
2323
.process("dist/contentScope.js"),
24+
.process("dist/contentScopeIsolated.js"),
2425
.copy("dist/pages"),
2526
]
2627
),

integration-test/playwright/page-objects/duckplayer-overlays.js

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { readFileSync } from 'fs'
22
import {
3-
mockResponses,
3+
mockResponses, mockWebkitMessaging,
44
mockWindowsMessaging,
5-
PlatformInfo,
6-
readOutgoingMessages, simulateSubscriptionMessage, waitForCallCount,
5+
readOutgoingMessages, simulateSubscriptionMessage, waitForCallCount, wrapWebkitScripts,
76
wrapWindowsScripts
87
} from '@duckduckgo/messaging/lib/test-utils.mjs'
98
import { expect } from '@playwright/test'
9+
import { perPlatform } from '../type-helpers.mjs'
1010

1111
// Every possible combination of UserValues
1212
const userValues = {
@@ -38,10 +38,12 @@ export class DuckplayerOverlays {
3838
serpProxyPage = '/duckplayer/pages/serp-proxy.html'
3939
/**
4040
* @param {import("@playwright/test").Page} page
41+
* @param {import("../type-helpers.mjs").Build} build
4142
* @param {import("@duckduckgo/messaging/lib/test-utils.mjs").PlatformInfo} platform
4243
*/
43-
constructor (page, platform) {
44+
constructor (page, build, platform) {
4445
this.page = page
46+
this.build = build
4547
this.platform = platform
4648
page.on('console', (msg) => {
4749
console.log(msg.type(), msg.text())
@@ -65,7 +67,7 @@ export class DuckplayerOverlays {
6567
expect(calls).toMatchObject([
6668
{
6769
payload: {
68-
context: 'contentScopeScripts',
70+
context: this.messagingContext,
6971
featureName: 'duckPlayer',
7072
params: {},
7173
method: 'getUserValues',
@@ -121,13 +123,13 @@ export class DuckplayerOverlays {
121123
async userChangedSettingTo (setting) {
122124
await this.page.evaluate(simulateSubscriptionMessage, {
123125
messagingContext: {
124-
context: 'contentScopeScripts',
126+
context: this.messagingContext,
125127
featureName: 'duckPlayer',
126128
env: 'development'
127129
},
128130
name: 'onUserValuesChanged',
129131
payload: userValues[setting],
130-
platform: this.platform
132+
injectName: this.build.name
131133
})
132134
}
133135

@@ -188,7 +190,7 @@ export class DuckplayerOverlays {
188190
expect(messages).toMatchObject([
189191
{
190192
payload: {
191-
context: 'contentScopeScripts',
193+
context: this.messagingContext,
192194
featureName: 'duckPlayer',
193195
params: {
194196
href: 'duck://player/1'
@@ -199,15 +201,6 @@ export class DuckplayerOverlays {
199201
])
200202
}
201203

202-
/**
203-
* @return {string}
204-
*/
205-
get buildArtefact () {
206-
// we can add more platforms here later
207-
const buildArtefact = readFileSync('./build/windows/contentScope.js', 'utf8')
208-
return buildArtefact
209-
}
210-
211204
/**
212205
* This is a bit involved, but verifies that the built artefact behaves as expected
213206
* given a mocked messaging implementation
@@ -220,7 +213,12 @@ export class DuckplayerOverlays {
220213
const { config } = params
221214

222215
// read the built file from disk and do replacements
223-
const injectedJS = wrapWindowsScripts(this.buildArtefact, {
216+
const wrapFn = this.build.switch({
217+
'apple-isolated': () => wrapWebkitScripts,
218+
windows: () => wrapWindowsScripts
219+
})
220+
221+
const injectedJS = wrapFn(this.build.artifact, {
224222
$CONTENT_SCOPE$: config,
225223
$USER_UNPROTECTED_DOMAINS$: [],
226224
$USER_PREFERENCES$: {
@@ -229,11 +227,15 @@ export class DuckplayerOverlays {
229227
}
230228
})
231229

232-
// setup shared messaging mocks
233-
await this.page.addInitScript(mockWindowsMessaging, {
230+
const mockMessaging = this.build.switch({
231+
windows: () => mockWindowsMessaging,
232+
'apple-isolated': () => mockWebkitMessaging
233+
})
234+
235+
await this.page.addInitScript(mockMessaging, {
234236
messagingContext: {
235237
env: 'development',
236-
context: 'contentScopeScripts',
238+
context: this.messagingContext,
237239
featureName: 'duckPlayer'
238240
},
239241
responses: {
@@ -275,7 +277,7 @@ export class DuckplayerOverlays {
275277
expect(messages).toMatchObject([
276278
{
277279
payload: {
278-
context: 'contentScopeScripts',
280+
context: this.messagingContext,
279281
featureName: 'duckPlayer',
280282
params: userValues[setting],
281283
method: 'setUserValues'
@@ -291,17 +293,14 @@ export class DuckplayerOverlays {
291293
*/
292294
static create (page, testInfo) {
293295
// Read the configuration object to determine which platform we're testing against
294-
if (!('platform' in testInfo.project.use)) {
295-
throw new Error('unsupported project - missing `use.platform`')
296-
}
297-
298-
let testPlatform
299-
if (testInfo.project.use.platform === 'windows') {
300-
testPlatform = new PlatformInfo({ name: testInfo.project.use.platform })
301-
} else {
302-
throw new Error('unsupported platform name: ' + testInfo.project.use.platform)
303-
}
304-
return new DuckplayerOverlays(page, testPlatform)
296+
const { platformInfo, build } = perPlatform(testInfo.project.use)
297+
return new DuckplayerOverlays(page, build, platformInfo)
298+
}
299+
300+
get messagingContext () {
301+
return this.build.name === 'apple-isolated'
302+
? 'contentScopeScriptsIsolated'
303+
: 'contentScopeScripts'
305304
}
306305
}
307306

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { readFileSync } from 'node:fs'
2+
3+
/**
4+
* Allows per-platform values. The 'platform' string is powered from globals.d.ts
5+
* and it represents an artifact name.
6+
*
7+
* For example
8+
*
9+
* - windows
10+
* - apple
11+
* - apple-isolated
12+
*
13+
* @template {NonNullable<ImportMeta['platform']>} T
14+
* @template {() => any} VariantFn
15+
* @param {T} name
16+
* @param {Partial<Record<NonNullable<ImportMeta['platform']>, VariantFn>>} switchItems
17+
* @returns {ReturnType<VariantFn>}
18+
*/
19+
export function platform (name, switchItems) {
20+
if (name in switchItems) {
21+
const fn = switchItems[name]
22+
if (!fn) {
23+
throw new Error('missing impl for that')
24+
}
25+
return fn()
26+
}
27+
throw new Error('missing impl for that')
28+
}
29+
30+
export class Build {
31+
/**
32+
* @param {NonNullable<ImportMeta['injectName']>} name
33+
*/
34+
constructor (name) {
35+
this.name = name
36+
}
37+
38+
/**
39+
* @template {() => any} VariantFn
40+
* @param {Partial<Record<NonNullable<ImportMeta['injectName']>, VariantFn>>} switchItems
41+
* @returns {ReturnType<VariantFn>}
42+
*/
43+
switch (switchItems) {
44+
if (this.name in switchItems) {
45+
const fn = switchItems[this.name]
46+
if (!fn) {
47+
throw new Error('missing impl for that')
48+
}
49+
return fn()
50+
}
51+
throw new Error('missing impl for that')
52+
}
53+
54+
/**
55+
*
56+
* @returns string
57+
*/
58+
get artifact () {
59+
const path = this.switch({
60+
windows: () => 'build/windows/contentScope.js',
61+
'apple-isolated': () => './Sources/ContentScopeScripts/dist/contentScopeIsolated.js'
62+
})
63+
return readFileSync(path, 'utf8')
64+
}
65+
66+
/**
67+
* @param {any} name
68+
* @returns {ImportMeta['injectName']}
69+
*/
70+
static supported (name) {
71+
/** @type {ImportMeta['injectName'][]} */
72+
const items = ['apple', 'apple-isolated', 'windows', 'integration']
73+
if (items.includes(name)) {
74+
return name
75+
}
76+
return undefined
77+
}
78+
}
79+
80+
export class PlatformInfo {
81+
/**
82+
* @param {object} params
83+
* @param {ImportMeta['platform']} params.name
84+
*/
85+
constructor (params) {
86+
this.name = params.name
87+
}
88+
89+
/**
90+
* @param {any} name
91+
* @returns {ImportMeta['platform']}
92+
*/
93+
static supported (name) {
94+
/** @type {ImportMeta['platform'][]} */
95+
const items = ['macos', 'windows']
96+
if (items.includes(name)) {
97+
return name
98+
}
99+
return undefined
100+
}
101+
}
102+
103+
/**
104+
* This takes the `use` part of the playwright config for each platform,
105+
* and then uses it to provide helpers to tests, such as looking up build artifacts.
106+
*
107+
* @param config
108+
* @returns {{build: Build; platformInfo: PlatformInfo}}
109+
*/
110+
export function perPlatform (config) {
111+
// Read the configuration object to determine which platform we're testing against
112+
if (!('injectName' in config) || typeof config.injectName !== 'string') {
113+
throw new Error('unsupported project - missing `use.injectName`')
114+
}
115+
116+
if (!('platform' in config) || typeof config.platform !== 'string') {
117+
throw new Error('unsupported project - missing `use.platform`')
118+
}
119+
120+
const name = Build.supported(config.injectName)
121+
if (name) {
122+
const build = new Build(name)
123+
const platform = PlatformInfo.supported(config.platform)
124+
if (platform) {
125+
const platformInfo = new PlatformInfo({ name: platform })
126+
return { build, platformInfo }
127+
}
128+
}
129+
130+
// If we get here, it's a mis-configuration
131+
throw new Error('unreachable')
132+
}

integration-test/playwright/windows-permissions.spec.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { test, expect } from '@playwright/test'
22
import { readFileSync } from 'fs'
33
import { mockWindowsMessaging, wrapWindowsScripts } from '@duckduckgo/messaging/lib/test-utils.mjs'
4+
import { perPlatform } from './type-helpers.mjs'
45

5-
test('Windows Permissions Usage', async ({ page }) => {
6-
const perms = new WindowsPermissionsSpec(page)
6+
test('Windows Permissions Usage', async ({ page }, testInfo) => {
7+
const perms = WindowsPermissionsSpec.create(page, testInfo)
78
await perms.enabled()
89
const results = await page.evaluate(() => {
910
// @ts-expect-error - this is added by the test framework
@@ -18,12 +19,15 @@ test('Windows Permissions Usage', async ({ page }) => {
1819
export class WindowsPermissionsSpec {
1920
htmlPage = '/permissions/index.html'
2021
config = './integration-test/test-pages/permissions/config/permissions.json'
21-
build = './build/windows/contentScope.js'
2222
/**
2323
* @param {import("@playwright/test").Page} page
24+
* @param {import("./type-helpers.mjs").Build} build
25+
* @param {import("./type-helpers.mjs").PlatformInfo} platform
2426
*/
25-
constructor (page) {
27+
constructor (page, build, platform) {
2628
this.page = page
29+
this.build = build
30+
this.platform = platform
2731
}
2832

2933
async enabled () {
@@ -72,7 +76,7 @@ export class WindowsPermissionsSpec {
7276
const { config } = params
7377

7478
// read the built file from disk and do replacements
75-
const injectedJS = wrapWindowsScripts(this.buildArtefact, {
79+
const injectedJS = wrapWindowsScripts(this.build.artifact, {
7680
$CONTENT_SCOPE$: config,
7781
$USER_UNPROTECTED_DOMAINS$: [],
7882
$USER_PREFERENCES$: {
@@ -95,10 +99,13 @@ export class WindowsPermissionsSpec {
9599
}
96100

97101
/**
98-
* @return {string}
102+
* Helper for creating an instance per platform
103+
* @param {import("@playwright/test").Page} page
104+
* @param {import("@playwright/test").TestInfo} testInfo
99105
*/
100-
get buildArtefact () {
101-
const buildArtefact = readFileSync(this.build, 'utf8')
102-
return buildArtefact
106+
static create (page, testInfo) {
107+
// Read the configuration object to determine which platform we're testing against
108+
const { platformInfo, build } = perPlatform(testInfo.project.use)
109+
return new WindowsPermissionsSpec(page, build, platformInfo)
103110
}
104111
}

0 commit comments

Comments
 (0)