2025-06-27 23:00:02 +08:00
/ * *
* Copyright 2019 Google Inc . All rights reserved .
* Modifications copyright ( c ) Microsoft Corporation .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2025-07-01 16:43:16 +08:00
import type { BrowserType , BrowserContext } from 'playwright-core' ;
import { playwrightTest as base , expect } from '../../config/browserTest' ;
const it = base . extend < {
launchPersistentContext : ( extensionPath : string , options? : Parameters < BrowserType [ ' launchPersistentContext ' ] > [ 1 ] ) = > Promise < BrowserContext > ;
2025-08-26 22:32:03 +08:00
} > ( {
launchPersistentContext : async ( { browserType } , use ) = > {
const browsers : BrowserContext [ ] = [ ] ;
await use ( async ( extensionPath , options = { } ) = > {
const extensionOptions = {
. . . options ,
args : [
` --disable-extensions-except= ${ extensionPath } ` ,
` --load-extension= ${ extensionPath } ` ,
] ,
} ;
return await browserType . launchPersistentContext ( '' , extensionOptions ) ;
} ) ;
await Promise . all ( browsers . map ( browser = > browser . close ( ) ) ) ;
}
} ) ;
2025-06-27 23:00:02 +08:00
it . skip ( ( { isHeadlessShell } ) = > isHeadlessShell , 'Headless Shell has no support for extensions' ) ;
it . describe ( 'MV3' , ( ) = > {
2025-07-01 16:43:16 +08:00
it . skip ( ( { channel } ) = > channel ? . startsWith ( 'chrome' ) , '--load-extension is not supported in Chrome anymore. https://groups.google.com/a/chromium.org/g/chromium-extensions/c/1-g8EFx2BBY/m/S0ET5wPjCAAJ' ) ;
2025-07-25 20:03:03 +08:00
it ( 'should give access to the service worker' , async ( { launchPersistentContext , asset } ) = > {
2025-06-27 23:00:02 +08:00
const extensionPath = asset ( 'extension-mv3-simple' ) ;
2025-07-01 16:43:16 +08:00
const context = await launchPersistentContext ( extensionPath ) ;
2025-06-27 23:00:02 +08:00
const serviceWorkers = context . serviceWorkers ( ) ;
const serviceWorker = serviceWorkers . length ? serviceWorkers [ 0 ] : await context . waitForEvent ( 'serviceworker' ) ;
expect ( serviceWorker ) . toBeTruthy ( ) ;
expect ( context . serviceWorkers ( ) ) . toContain ( serviceWorker ) ;
2025-07-01 16:43:16 +08:00
await expect . poll ( ( ) = > serviceWorker . evaluate ( ( ) = > ( globalThis as any ) . MAGIC ) ) . toBe ( 42 ) ;
2025-06-27 23:00:02 +08:00
await context . close ( ) ;
expect ( context . backgroundPages ( ) . length ) . toBe ( 0 ) ;
} ) ;
2025-07-25 20:03:03 +08:00
it ( 'should give access to the service worker when recording video' , async ( { launchPersistentContext , asset } , testInfo ) = > {
2025-06-27 23:00:02 +08:00
const extensionPath = asset ( 'extension-mv3-simple' ) ;
2025-07-01 16:43:16 +08:00
const context = await launchPersistentContext ( extensionPath , {
2025-06-27 23:00:02 +08:00
recordVideo : {
dir : testInfo.outputPath ( '' ) ,
2025-07-01 16:43:16 +08:00
}
} ) ;
2025-06-27 23:00:02 +08:00
const serviceWorkers = context . serviceWorkers ( ) ;
const serviceWorker = serviceWorkers . length ? serviceWorkers [ 0 ] : await context . waitForEvent ( 'serviceworker' ) ;
expect ( serviceWorker ) . toBeTruthy ( ) ;
expect ( context . serviceWorkers ( ) ) . toContain ( serviceWorker ) ;
2025-07-01 16:43:16 +08:00
await expect . poll ( ( ) = > serviceWorker . evaluate ( ( ) = > ( globalThis as any ) . MAGIC ) ) . toBe ( 42 ) ;
2025-06-27 23:00:02 +08:00
await context . close ( ) ;
} ) ;
2025-07-25 20:03:03 +08:00
it ( 'should support request/response events in the service worker' , async ( { launchPersistentContext , asset , server } ) = > {
2025-07-02 16:23:00 +08:00
it . fixme ( true , 'Waiting for https://issues.chromium.org/u/1/issues/407795731 getting fixed.' ) ;
2025-06-27 23:00:02 +08:00
process . env . PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS = '1' ;
server . setRoute ( '/empty.html' , ( req , res ) = > {
res . writeHead ( 200 , { 'Content-Type' : 'text/html' , 'x-response-foobar' : 'BarFoo' } ) ;
res . end ( ` <span>hello world!</span> ` ) ;
} ) ;
const extensionPath = asset ( 'extension-mv3-simple' ) ;
2025-07-01 16:43:16 +08:00
const context = await launchPersistentContext ( extensionPath ) ;
2025-06-27 23:00:02 +08:00
const serviceWorkers = context . serviceWorkers ( ) ;
const serviceWorker = serviceWorkers . length ? serviceWorkers [ 0 ] : await context . waitForEvent ( 'serviceworker' ) ;
expect ( serviceWorker . url ( ) ) . toMatch ( /chrome-extension\:\/\/.*/ ) ;
const [ request , response ] = await Promise . all ( [
context . waitForEvent ( 'request' ) ,
context . waitForEvent ( 'response' ) ,
serviceWorker . evaluate ( url = > fetch ( url , {
method : 'POST' ,
body : 'foobar' ,
headers : { 'X-FOOBAR' : 'KEKBAR' }
} ) , server . EMPTY_PAGE ) ,
] ) ;
expect ( request . url ( ) ) . toBe ( server . EMPTY_PAGE ) ;
expect ( request . method ( ) ) . toBe ( 'POST' ) ;
expect ( await request . allHeaders ( ) ) . toEqual ( expect . objectContaining ( { 'x-foobar' : 'KEKBAR' } ) ) ;
expect ( request . postData ( ) ) . toBe ( 'foobar' ) ;
expect ( response . status ( ) ) . toBe ( 200 ) ;
expect ( response . url ( ) ) . toBe ( server . EMPTY_PAGE ) ;
expect ( response . request ( ) ) . toBe ( request ) ;
expect ( await response . text ( ) ) . toBe ( '<span>hello world!</span>' ) ;
expect ( await response . allHeaders ( ) ) . toEqual ( expect . objectContaining ( { 'x-response-foobar' : 'BarFoo' } ) ) ;
await context . close ( ) ;
delete process . env . PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS ;
} ) ;
2025-07-01 16:43:16 +08:00
it ( 'should report console messages from content script' , {
2025-06-27 23:00:02 +08:00
annotation : { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/32762' }
2025-07-01 16:43:16 +08:00
} , async ( { launchPersistentContext , asset , server } ) = > {
2025-06-27 23:00:02 +08:00
const extensionPath = asset ( 'extension-mv3-with-logging' ) ;
2025-07-01 16:43:16 +08:00
const context = await launchPersistentContext ( extensionPath ) ;
2025-06-27 23:00:02 +08:00
const page = await context . newPage ( ) ;
const consolePromise = page . waitForEvent ( 'console' , e = > e . text ( ) . includes ( 'Test console log from a third-party execution context' ) ) ;
await page . goto ( server . EMPTY_PAGE ) ;
const message = await consolePromise ;
expect ( message . text ( ) ) . toContain ( 'Test console log from a third-party execution context' ) ;
await context . close ( ) ;
} ) ;
} ) ;