Skip to content

Commit 453ac85

Browse files
authored
Merge pull request #20119 from mozilla/react-pair
feat(functional-tests): add pairing E2E test with marionette authority
2 parents fc5826e + ee5cbf3 commit 453ac85

7 files changed

Lines changed: 1889 additions & 0 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
/**
6+
* Playwright fixture extension for pairing E2E tests.
7+
*
8+
* Adds a `marionetteAuthority` fixture that launches a separate Firefox
9+
* instance with Marionette enabled, suitable for driving the authority
10+
* (desktop) side of the pairing flow.
11+
*/
12+
13+
import { firefox } from 'playwright';
14+
import { MarionetteFirefox } from '../marionette-firefox';
15+
import { test as standardTest, TestOptions } from './standard';
16+
17+
export type PairingTestOptions = TestOptions & {
18+
marionetteAuthority: MarionetteFirefox;
19+
};
20+
21+
export const test = standardTest.extend<PairingTestOptions>({
22+
marionetteAuthority: async ({ target }, use, testInfo) => {
23+
// Use Playwright's bundled Firefox by default — it's already downloaded
24+
// in CI and locally. Override with FIREFOX_BINARY env if needed.
25+
const firefoxBinary =
26+
process.env.FIREFOX_BINARY || firefox.executablePath();
27+
const channelServerUri =
28+
process.env.CHANNEL_SERVER_URI ||
29+
(await fetchChannelServerUri(target.contentServerUrl));
30+
const marionettePort = parseInt(process.env.MARIONETTE_PORT || '2828', 10);
31+
if (isNaN(marionettePort)) {
32+
throw new Error(
33+
`Invalid MARIONETTE_PORT: ${process.env.MARIONETTE_PORT}`
34+
);
35+
}
36+
const headless = process.env.MARIONETTE_HEADLESS !== 'false';
37+
38+
const authority = await MarionetteFirefox.launch({
39+
firefoxBinary,
40+
marionettePort,
41+
channelServerUri,
42+
target: target.name,
43+
context: 'oauth_webchannel_v1',
44+
headless,
45+
});
46+
47+
// In CI, inject WAF bypass header into all Firefox HTTP requests
48+
const wafToken = process.env.CI_WAF_TOKEN;
49+
if (process.env.CI && wafToken) {
50+
await authority.client.setContext('chrome');
51+
await authority.client.executeScript(
52+
`
53+
const token = arguments[0];
54+
Services.obs.addObserver({
55+
observe(subject) {
56+
subject.QueryInterface(Ci.nsIHttpChannel);
57+
subject.setRequestHeader("fxa-ci", token, false);
58+
}
59+
}, "http-on-modify-request");
60+
`,
61+
{ sandbox: 'system', args: [wafToken] }
62+
);
63+
}
64+
65+
await use(authority);
66+
67+
await authority.close();
68+
},
69+
});
70+
71+
export { expect } from '@playwright/test';
72+
73+
/**
74+
* Fetch the pairing channel server URI from the target's well-known config.
75+
* Each environment (local, stage, production) uses a different channel server.
76+
*/
77+
async function fetchChannelServerUri(
78+
contentServerUrl: string
79+
): Promise<string> {
80+
const fallback = 'wss://channelserver.services.mozilla.com';
81+
try {
82+
const url = `${contentServerUrl}/.well-known/fxa-client-configuration`;
83+
const headers: Record<string, string> =
84+
process.env.CI && process.env.CI_WAF_TOKEN
85+
? { 'fxa-ci': process.env.CI_WAF_TOKEN }
86+
: {};
87+
const resp = await fetch(url, { headers });
88+
if (!resp.ok) return fallback;
89+
const config = await resp.json();
90+
return config.pairing_server_base_uri || fallback;
91+
} catch {
92+
return fallback;
93+
}
94+
}

0 commit comments

Comments
 (0)