1414 */
1515
1616import crypto from 'crypto' ;
17- import { expect , Page } from '@playwright/test' ;
17+ import { Browser , expect , Page } from '@playwright/test' ;
18+ import { ConfigPage } from '../pages/config' ;
19+ import { BaseTarget } from './targets/base' ;
1820import { MarionetteClient } from './marionette' ;
1921import {
2022 PAIRING_CLIENT_ID ,
@@ -26,22 +28,39 @@ import {
2628import { getTotpCode } from './totp' ;
2729
2830/**
29- * Fetch the content server's fxa-config and check whether React pairing
30- * routes are enabled (showReactApp.pairRoutes). When enabled, the Backbone
31- * /pair/* routes are deregistered and only React (fxa-settings) serves them.
31+ * Check whether React pairing routes are enabled (showReactApp.pairRoutes).
32+ * When enabled, the Backbone /pair/* routes are deregistered and only React
33+ * (fxa-settings) serves them.
34+ *
35+ * Reuses the shared ConfigPage helper, which opens a real Playwright page and
36+ * reads the fxa-config meta tag. This matters for stage and production,
37+ * which are behind Fastly's Next-Gen WAF: a plain fetch() receives the
38+ * JavaScript "Client Challenge" interstitial instead of the real HTML. A
39+ * browser page executes the challenge and then renders the real page with
40+ * the meta tag.
41+ *
42+ * Callers should invoke this from `test.beforeAll` so it runs once per
43+ * worker; pair-route rollout is stable for the lifetime of a test run.
3244 */
3345export async function isPairRoutesReact (
34- contentServerUrl : string
46+ browser : Browser ,
47+ target : BaseTarget
3548) : Promise < boolean > {
49+ // Mirror playwright.config.ts's `use.extraHTTPHeaders` so requests made from
50+ // this helper also carry the WAF bypass token in CI. Without this, the WAF
51+ // serves its JS interstitial and the fxa-config meta tag never renders.
52+ const extraHTTPHeaders : Record < string , string > = { } ;
53+ target . ciHeader ?. forEach ( ( value , key ) => {
54+ extraHTTPHeaders [ key ] = value ;
55+ } ) ;
56+ const context = await browser . newContext ( { extraHTTPHeaders } ) ;
57+ const page = await context . newPage ( ) ;
3658 try {
37- const resp = await fetch ( contentServerUrl ) ;
38- const html = await resp . text ( ) ;
39- const match = html . match ( / n a m e = " f x a - c o n f i g " c o n t e n t = " ( [ ^ " ] + ) " / ) ;
40- if ( ! match ) return false ;
41- const config = JSON . parse ( decodeURIComponent ( match [ 1 ] ) ) ;
42- return config . showReactApp ?. pairRoutes === true ;
43- } catch {
44- return false ;
59+ const configPage = new ConfigPage ( page , target ) ;
60+ const config = await configPage . getConfig ( ) ;
61+ return config ?. showReactApp ?. pairRoutes === true ;
62+ } finally {
63+ await context . close ( ) ;
4564 }
4665}
4766
0 commit comments