Skip to content

Commit 8e76460

Browse files
authored
enable messageBridge on Android + tests (#1395)
* enable messageBridge on Android + tests * fixed tests * fixing duckplayer special-pages tests * privacy config
1 parent 2145e03 commit 8e76460

File tree

8 files changed

+113
-5
lines changed

8 files changed

+113
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { test, expect } from '@playwright/test';
2+
import { ResultsCollector } from './page-objects/results-collector.js';
3+
import { readOutgoingMessages } from '@duckduckgo/messaging/lib/test-utils.mjs';
4+
5+
const ENABLED_CONFIG = 'integration-test/test-pages/message-bridge/config/message-bridge-enabled.json';
6+
const DISABLED_CONFIG = 'integration-test/test-pages/message-bridge/config/message-bridge-disabled.json';
7+
const ENABLED_HTML = '/message-bridge/pages/enabled.html';
8+
const DISABLED_HTML = '/message-bridge/pages/disabled.html';
9+
10+
test('message bridge when enabled (android)', async ({ page }, testInfo) => {
11+
const pageWorld = ResultsCollector.create(page, testInfo.project.use);
12+
13+
// seed the request->re
14+
pageWorld.withMockResponse({
15+
sampleData: /** @type {any} */ ({
16+
ghi: 'jkl',
17+
}),
18+
});
19+
20+
pageWorld.withUserPreferences({
21+
messageSecret: 'ABC',
22+
javascriptInterface: 'javascriptInterface',
23+
messageCallback: 'messageCallback',
24+
});
25+
26+
// now load the page
27+
await pageWorld.load(ENABLED_HTML, ENABLED_CONFIG);
28+
29+
// simulate a push event
30+
await pageWorld.simulateSubscriptionMessage('exampleFeature', 'onUpdate', { abc: 'def' });
31+
32+
// get all results
33+
const results = await pageWorld.results();
34+
expect(results['Creating the bridge']).toStrictEqual([
35+
{ name: 'bridge.notify', result: 'function', expected: 'function' },
36+
{ name: 'bridge.request', result: 'function', expected: 'function' },
37+
{ name: 'bridge.subscribe', result: 'function', expected: 'function' },
38+
{ name: 'data', result: [{ abc: 'def' }, { ghi: 'jkl' }], expected: [{ abc: 'def' }, { ghi: 'jkl' }] },
39+
]);
40+
41+
// verify messaging calls
42+
const calls = await page.evaluate(readOutgoingMessages);
43+
expect(calls.length).toBe(2);
44+
const pixel = calls[0].payload;
45+
const request = calls[1].payload;
46+
47+
expect(pixel).toStrictEqual({
48+
context: 'contentScopeScripts',
49+
featureName: 'exampleFeature',
50+
method: 'pixel',
51+
params: {},
52+
});
53+
54+
const { id, ...rest } = /** @type {import("@duckduckgo/messaging").RequestMessage} */ (request);
55+
56+
expect(rest).toStrictEqual({
57+
context: 'contentScopeScripts',
58+
featureName: 'exampleFeature',
59+
method: 'sampleData',
60+
params: {},
61+
});
62+
63+
if (!('id' in request)) throw new Error('unreachable');
64+
65+
expect(typeof request.id).toBe('string');
66+
expect(request.id.length).toBeGreaterThan(10);
67+
});
68+
69+
test('message bridge when disabled (android)', async ({ page }, testInfo) => {
70+
const pageWorld = ResultsCollector.create(page, testInfo.project.use);
71+
72+
// now load the main page
73+
await pageWorld.load(DISABLED_HTML, DISABLED_CONFIG);
74+
75+
// verify no outgoing calls were made
76+
const calls = await page.evaluate(readOutgoingMessages);
77+
expect(calls).toHaveLength(0);
78+
79+
// get all results
80+
const results = await pageWorld.results();
81+
expect(results['Creating the bridge, but it is unavailable']).toStrictEqual([
82+
{ name: 'error', result: 'Did not install Message Bridge', expected: 'Did not install Message Bridge' },
83+
]);
84+
});

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

+5
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ export class DuckplayerOverlays {
8080
},
8181
sendDuckPlayerPixel: {},
8282
});
83+
this.collector.withUserPreferences({
84+
messageSecret: 'ABC',
85+
javascriptInterface: 'javascriptInterface',
86+
messageCallback: 'messageCallback',
87+
});
8388
page.on('console', (msg) => {
8489
console.log(msg.type(), msg.text());
8590
});

injected/integration-test/page-objects/results-collector.js

+3
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ export class ResultsCollector {
172172
messagingContext: this.messagingContext('n/a'),
173173
responses: this.#mockResponses,
174174
messageCallback: 'messageCallback',
175+
javascriptInterface: this.#userPreferences.javascriptInterface,
175176
});
176177

177178
const wrapFn = this.build.switch({
@@ -234,6 +235,8 @@ export class ResultsCollector {
234235
name,
235236
payload,
236237
injectName: this.build.name,
238+
messageCallback: this.#userPreferences.messageCallback,
239+
messageSecret: this.#userPreferences.messageSecret,
237240
});
238241
}
239242

injected/playwright.config.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ export default defineConfig({
4141
},
4242
{
4343
name: 'android',
44-
testMatch: ['integration-test/duckplayer-mobile.spec.js', 'integration-test/web-compat-android.spec.js'],
44+
testMatch: [
45+
'integration-test/duckplayer-mobile.spec.js',
46+
'integration-test/web-compat-android.spec.js',
47+
'integration-test/message-bridge-android.spec.js',
48+
],
4549
use: { injectName: 'android', platform: 'android', ...devices['Galaxy S5'] },
4650
},
4751
{

injected/src/features.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const otherFeatures = /** @type {const} */ ([
3333
export const platformSupport = {
3434
apple: ['webCompat', ...baseFeatures],
3535
'apple-isolated': ['duckPlayer', 'brokerProtection', 'performanceMetrics', 'clickToLoad', 'messageBridge'],
36-
android: [...baseFeatures, 'webCompat', 'breakageReporting', 'duckPlayer'],
36+
android: [...baseFeatures, 'webCompat', 'breakageReporting', 'duckPlayer', 'messageBridge'],
3737
'android-autofill-password-import': ['autofillPasswordImport'],
3838
windows: ['cookie', ...baseFeatures, 'windowsPermissionUsage', 'duckPlayer', 'brokerProtection', 'breakageReporting'],
3939
firefox: ['cookie', ...baseFeatures, 'clickToLoad'],

messaging/lib/test-utils.mjs

+13-2
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ export function mockWebkitMessaging(params) {
274274
* messagingContext: import('../index.js').MessagingContext,
275275
* responses: Record<string, any>,
276276
* messageCallback: string
277+
* javascriptInterface?: string
277278
* }} params
278279
*/
279280
export function mockAndroidMessaging(params) {
@@ -284,7 +285,8 @@ export function mockAndroidMessaging(params) {
284285
outgoing: [],
285286
},
286287
};
287-
window[params.messagingContext.context] = {
288+
if (!params.javascriptInterface) throw new Error('`javascriptInterface` is required for Android mocking');
289+
window[params.javascriptInterface] = {
288290
/**
289291
* @param {string} jsonString
290292
* @param {string} secret
@@ -322,7 +324,7 @@ export function mockAndroidMessaging(params) {
322324
id: msg.id,
323325
};
324326

325-
globalThis.messageCallback?.(secret, r);
327+
globalThis[params.messageCallback]?.(secret, r);
326328
},
327329
};
328330
}
@@ -406,6 +408,8 @@ export function wrapWebkitScripts(js, replacements) {
406408
* @param {string} params.name
407409
* @param {Record<string, any>} params.payload
408410
* @param {NonNullable<ImportMeta['injectName']>} params.injectName
411+
* @param {string} [params.messageCallback] - optional name of a global method where messages can be delivered (android)
412+
* @param {string} [params.messageSecret] - optional message secret for platforms that require it (android)
409413
*/
410414
export function simulateSubscriptionMessage(params) {
411415
const subscriptionEvent = {
@@ -421,6 +425,13 @@ export function simulateSubscriptionMessage(params) {
421425
fn(subscriptionEvent);
422426
break;
423427
}
428+
case 'android': {
429+
if (!params.messageCallback || !params.messageSecret)
430+
throw new Error('`messageCallback` + `messageSecret` needed to simulate subscription event on Android');
431+
432+
window[params.messageCallback]?.(params.messageSecret, subscriptionEvent);
433+
break;
434+
}
424435
case 'apple':
425436
case 'apple-isolated': {
426437
if (!(params.name in window)) throw new Error('subscription fn not found for: ' + params.injectName);

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

special-pages/shared/mocks.js

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export class Mocks {
5959
messagingContext: this.messagingContext,
6060
responses: this._defaultResponses,
6161
messageCallback: 'messageCallback',
62+
javascriptInterface: this.messagingContext.context,
6263
});
6364
},
6465
integration: async () => {

0 commit comments

Comments
 (0)