@@ -23,7 +23,7 @@ const { DOWNLOAD_DIR, cleanupDownloads, waitFor } = require('../../helpers/exten
2323
2424// ── Constants ──────────────────────────────────────────────────────────────────
2525const EXTENSION_PATH = path . resolve ( __dirname , '../../../dist/chrome' ) ;
26- const ARIA2_PORT = 16800 ;
26+ const ARIA2_PORT = 16900 ;
2727const FILE_SERVER_PORT = 8080 ;
2828const TEST_API_KEY = 'e2e-test-secret' ;
2929
@@ -130,6 +130,36 @@ async function suppressExtensionNotifications() {
130130 } ) ;
131131}
132132
133+ /** Read a value from chrome.storage.local. */
134+ async function readLocalStorage ( key ) {
135+ const page = await context . newPage ( ) ;
136+ try {
137+ await page . goto ( `chrome-extension://${ extensionId } /pages/config.html` , {
138+ waitUntil : 'domcontentloaded' ,
139+ } ) ;
140+ return await page . evaluate (
141+ ( k ) => new Promise ( ( r ) => chrome . storage . local . get ( k , ( res ) => r ( res [ k ] ) ) ) ,
142+ key
143+ ) ;
144+ } finally {
145+ await page . close ( ) ;
146+ }
147+ }
148+
149+ /** Write values into chrome.storage.local. */
150+ async function setLocalStorage ( obj ) {
151+ const page = await context . newPage ( ) ;
152+ try {
153+ await page . goto ( `chrome-extension://${ extensionId } /pages/config.html` , {
154+ waitUntil : 'domcontentloaded' ,
155+ } ) ;
156+ await page . evaluate ( ( o ) => chrome . storage . local . set ( o ) , obj ) ;
157+ await page . waitForTimeout ( 200 ) ;
158+ } finally {
159+ await page . close ( ) ;
160+ }
161+ }
162+
133163/** Open a fresh page pointing at the file server index. */
134164async function openFileServerPage ( ) {
135165 const page = await context . newPage ( ) ;
@@ -656,13 +686,13 @@ test.describe('Prompt Before Download', () => {
656686 } ) ;
657687
658688 test ( 'download is intercepted when prompt_for_download is enabled' , async ( ) => {
659- const mockAria2Pbd = new MockAria2Server ( 16_801 ) ;
689+ const mockAria2Pbd = new MockAria2Server ( 16_901 ) ;
660690 await mockAria2Pbd . start ( ) ;
661691
662692 try {
663693 const configPage = await pbdContext . newPage ( ) ;
664694 await configPage . goto ( `chrome-extension://${ pbdExtensionId } /pages/config.html` , { waitUntil : 'domcontentloaded' } ) ;
665- await configPage . evaluate ( async ( s ) => { await chrome . storage . sync . set ( s ) ; } , { ...DEFAULT_SETTINGS , motrixPort : 16_801 } ) ;
695+ await configPage . evaluate ( async ( s ) => { await chrome . storage . sync . set ( s ) ; } , { ...DEFAULT_SETTINGS , motrixPort : 16_901 } ) ;
666696 await configPage . waitForTimeout ( 400 ) ;
667697 await configPage . close ( ) ;
668698
@@ -679,6 +709,75 @@ test.describe('Prompt Before Download', () => {
679709 } ) ;
680710} ) ;
681711
712+ // ══════════════════════════════════════════════════════════════════════════════
713+ // 8. Motrix Reachability
714+ // ══════════════════════════════════════════════════════════════════════════════
715+ test . describe ( 'Motrix Reachability' , ( ) => {
716+ test . afterEach ( async ( ) => {
717+ await setLocalStorage ( { motrixReachable : null } ) ;
718+ } ) ;
719+
720+ test ( 'motrixReachable is set to true after a successful intercept' , async ( ) => {
721+ const page = await openFileServerPage ( ) ;
722+ await page . click ( '#large-download' ) ;
723+ await waitFor ( ( ) => mockAria2 . getCalls ( 'addUri' ) . length > 0 , 25_000 ) ;
724+ await waitFor ( async ( ) => ( await readLocalStorage ( 'motrixReachable' ) ) === true , 5_000 ) ;
725+ expect ( await readLocalStorage ( 'motrixReachable' ) ) . toBe ( true ) ;
726+ await page . close ( ) ;
727+ } ) ;
728+
729+ test ( 'motrixReachable is set to false when Aria2 is unreachable' , async ( ) => {
730+ await configureExtension ( { motrixPort : 19_999 , downloadFallback : true } ) ;
731+ const page = await openFileServerPage ( ) ;
732+ await page . click ( '#mini-download' ) ;
733+ await waitFor ( async ( ) => ( await readLocalStorage ( 'motrixReachable' ) ) === false , 15_000 , 500 ) ;
734+ expect ( await readLocalStorage ( 'motrixReachable' ) ) . toBe ( false ) ;
735+ await page . close ( ) ;
736+ await restoreDefaults ( ) ;
737+ } ) ;
738+
739+ test ( 'popup shows reachability banner when motrixReachable is false' , async ( ) => {
740+ // Reject aria2 connections so the on-open ping also fails and doesn't clear the flag
741+ mockAria2 . setRejectConnections ( true ) ;
742+ try {
743+ await setLocalStorage ( { motrixReachable : false } ) ;
744+ const page = await context . newPage ( ) ;
745+ await page . goto ( `chrome-extension://${ extensionId } /pages/popup.html` , {
746+ waitUntil : 'networkidle' ,
747+ } ) ;
748+ await sleep ( 1_000 ) ;
749+ await expect ( page . locator ( 'text=Motrix is not reachable' ) ) . toBeVisible ( ) ;
750+ await page . close ( ) ;
751+ } finally {
752+ mockAria2 . setRejectConnections ( false ) ;
753+ }
754+ } ) ;
755+
756+ test ( 'popup does not show banner when motrixReachable is true' , async ( ) => {
757+ await setLocalStorage ( { motrixReachable : true } ) ;
758+ const page = await context . newPage ( ) ;
759+ await page . goto ( `chrome-extension://${ extensionId } /pages/popup.html` , {
760+ waitUntil : 'networkidle' ,
761+ } ) ;
762+ await sleep ( 500 ) ;
763+ await expect ( page . locator ( 'text=Motrix is not reachable' ) ) . toHaveCount ( 0 ) ;
764+ await page . close ( ) ;
765+ } ) ;
766+
767+ test ( 'opening popup with Motrix running clears the unreachable flag' , async ( ) => {
768+ await setLocalStorage ( { motrixReachable : false } ) ;
769+ const page = await context . newPage ( ) ;
770+ await page . goto ( `chrome-extension://${ extensionId } /pages/popup.html` , {
771+ waitUntil : 'networkidle' ,
772+ } ) ;
773+ // Popup sends checkMotrixStatus on mount; background pings mock aria2 (running)
774+ await waitFor ( async ( ) => ( await readLocalStorage ( 'motrixReachable' ) ) === true , 8_000 , 300 ) ;
775+ expect ( await readLocalStorage ( 'motrixReachable' ) ) . toBe ( true ) ;
776+ await expect ( page . locator ( 'text=Motrix is not reachable' ) ) . toHaveCount ( 0 ) ;
777+ await page . close ( ) ;
778+ } ) ;
779+ } ) ;
780+
682781// ══════════════════════════════════════════════════════════════════════════════
683782// 9. File Cleanup
684783// ══════════════════════════════════════════════════════════════════════════════
0 commit comments