Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enable messageBridge on Android + tests #1395

Merged
merged 4 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions injected/integration-test/message-bridge-android.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { test, expect } from '@playwright/test';
import { ResultsCollector } from './page-objects/results-collector.js';
import { readOutgoingMessages } from '@duckduckgo/messaging/lib/test-utils.mjs';

const ENABLED_CONFIG = 'integration-test/test-pages/message-bridge/config/message-bridge-enabled.json';
const DISABLED_CONFIG = 'integration-test/test-pages/message-bridge/config/message-bridge-disabled.json';
const ENABLED_HTML = '/message-bridge/pages/enabled.html';
const DISABLED_HTML = '/message-bridge/pages/disabled.html';

test('message bridge when enabled (android)', async ({ page }, testInfo) => {
const pageWorld = ResultsCollector.create(page, testInfo.project.use);

// seed the request->re
pageWorld.withMockResponse({
sampleData: /** @type {any} */ ({
ghi: 'jkl',
}),
});

pageWorld.withUserPreferences({
messageSecret: 'ABC',
javascriptInterface: 'javascriptInterface',
messageCallback: 'messageCallback',
});

// now load the page
await pageWorld.load(ENABLED_HTML, ENABLED_CONFIG);

// simulate a push event
await pageWorld.simulateSubscriptionMessage('exampleFeature', 'onUpdate', { abc: 'def' });

// get all results
const results = await pageWorld.results();
expect(results['Creating the bridge']).toStrictEqual([
{ name: 'bridge.notify', result: 'function', expected: 'function' },
{ name: 'bridge.request', result: 'function', expected: 'function' },
{ name: 'bridge.subscribe', result: 'function', expected: 'function' },
{ name: 'data', result: [{ abc: 'def' }, { ghi: 'jkl' }], expected: [{ abc: 'def' }, { ghi: 'jkl' }] },
]);

// verify messaging calls
const calls = await page.evaluate(readOutgoingMessages);
expect(calls.length).toBe(2);
const pixel = calls[0].payload;
const request = calls[1].payload;

expect(pixel).toStrictEqual({
context: 'contentScopeScripts',
featureName: 'exampleFeature',
method: 'pixel',
params: {},
});

const { id, ...rest } = /** @type {import("@duckduckgo/messaging").RequestMessage} */ (request);

expect(rest).toStrictEqual({
context: 'contentScopeScripts',
featureName: 'exampleFeature',
method: 'sampleData',
params: {},
});

if (!('id' in request)) throw new Error('unreachable');

expect(typeof request.id).toBe('string');
expect(request.id.length).toBeGreaterThan(10);
});

test('message bridge when disabled (android)', async ({ page }, testInfo) => {
const pageWorld = ResultsCollector.create(page, testInfo.project.use);

// now load the main page
await pageWorld.load(DISABLED_HTML, DISABLED_CONFIG);

// verify no outgoing calls were made
const calls = await page.evaluate(readOutgoingMessages);
expect(calls).toHaveLength(0);

// get all results
const results = await pageWorld.results();
expect(results['Creating the bridge, but it is unavailable']).toStrictEqual([
{ name: 'error', result: 'Did not install Message Bridge', expected: 'Did not install Message Bridge' },
]);
});
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export class DuckplayerOverlays {
},
sendDuckPlayerPixel: {},
});
this.collector.withUserPreferences({
messageSecret: 'ABC',
javascriptInterface: 'javascriptInterface',
messageCallback: 'messageCallback',
});
page.on('console', (msg) => {
console.log(msg.type(), msg.text());
});
Expand Down
3 changes: 3 additions & 0 deletions injected/integration-test/page-objects/results-collector.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export class ResultsCollector {
messagingContext: this.messagingContext('n/a'),
responses: this.#mockResponses,
messageCallback: 'messageCallback',
javascriptInterface: this.#userPreferences.javascriptInterface,
});

const wrapFn = this.build.switch({
Expand Down Expand Up @@ -234,6 +235,8 @@ export class ResultsCollector {
name,
payload,
injectName: this.build.name,
messageCallback: this.#userPreferences.messageCallback,
messageSecret: this.#userPreferences.messageSecret,
});
}

Expand Down
6 changes: 5 additions & 1 deletion injected/playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ export default defineConfig({
},
{
name: 'android',
testMatch: ['integration-test/duckplayer-mobile.spec.js', 'integration-test/web-compat-android.spec.js'],
testMatch: [
'integration-test/duckplayer-mobile.spec.js',
'integration-test/web-compat-android.spec.js',
'integration-test/message-bridge-android.spec.js',
],
use: { injectName: 'android', platform: 'android', ...devices['Galaxy S5'] },
},
{
Expand Down
2 changes: 1 addition & 1 deletion injected/src/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const otherFeatures = /** @type {const} */ ([
export const platformSupport = {
apple: ['webCompat', ...baseFeatures],
'apple-isolated': ['duckPlayer', 'brokerProtection', 'performanceMetrics', 'clickToLoad', 'messageBridge'],
android: [...baseFeatures, 'webCompat', 'breakageReporting', 'duckPlayer'],
android: [...baseFeatures, 'webCompat', 'breakageReporting', 'duckPlayer', 'messageBridge'],
'android-autofill-password-import': ['autofillPasswordImport'],
windows: ['cookie', ...baseFeatures, 'windowsPermissionUsage', 'duckPlayer', 'brokerProtection', 'breakageReporting'],
firefox: ['cookie', ...baseFeatures, 'clickToLoad'],
Expand Down
15 changes: 13 additions & 2 deletions messaging/lib/test-utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ export function mockWebkitMessaging(params) {
* messagingContext: import('../index.js').MessagingContext,
* responses: Record<string, any>,
* messageCallback: string
* javascriptInterface?: string
* }} params
*/
export function mockAndroidMessaging(params) {
Expand All @@ -284,7 +285,8 @@ export function mockAndroidMessaging(params) {
outgoing: [],
},
};
window[params.messagingContext.context] = {
if (!params.javascriptInterface) throw new Error('`javascriptInterface` is required for Android mocking');
window[params.javascriptInterface] = {
/**
* @param {string} jsonString
* @param {string} secret
Expand Down Expand Up @@ -322,7 +324,7 @@ export function mockAndroidMessaging(params) {
id: msg.id,
};

globalThis.messageCallback?.(secret, r);
globalThis[params.messageCallback]?.(secret, r);
},
};
}
Expand Down Expand Up @@ -406,6 +408,8 @@ export function wrapWebkitScripts(js, replacements) {
* @param {string} params.name
* @param {Record<string, any>} params.payload
* @param {NonNullable<ImportMeta['injectName']>} params.injectName
* @param {string} [params.messageCallback] - optional name of a global method where messages can be delivered (android)
* @param {string} [params.messageSecret] - optional message secret for platforms that require it (android)
*/
export function simulateSubscriptionMessage(params) {
const subscriptionEvent = {
Expand All @@ -421,6 +425,13 @@ export function simulateSubscriptionMessage(params) {
fn(subscriptionEvent);
break;
}
case 'android': {
if (!params.messageCallback || !params.messageSecret)
throw new Error('`messageCallback` + `messageSecret` needed to simulate subscription event on Android');

window[params.messageCallback]?.(params.messageSecret, subscriptionEvent);
break;
}
case 'apple':
case 'apple-isolated': {
if (!(params.name in window)) throw new Error('subscription fn not found for: ' + params.injectName);
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions special-pages/shared/mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export class Mocks {
messagingContext: this.messagingContext,
responses: this._defaultResponses,
messageCallback: 'messageCallback',
javascriptInterface: this.messagingContext.context,
});
},
integration: async () => {
Expand Down
Loading