|
| 1 | +import { |
| 2 | + test as base, |
| 3 | + chromium, |
| 4 | + type Page, |
| 5 | + type BrowserContext, |
| 6 | + type ElementHandle |
| 7 | +} from '@playwright/test' |
| 8 | + |
| 9 | +export const extensionFixtures = ( |
| 10 | + pathToExtension: string, |
| 11 | + headless: boolean |
| 12 | +) => { |
| 13 | + return base.extend<{ |
| 14 | + context: BrowserContext |
| 15 | + extensionId: string |
| 16 | + }>({ |
| 17 | + context: async ({}, use) => { |
| 18 | + const context = await chromium.launchPersistentContext('', { |
| 19 | + headless: false, |
| 20 | + args: [ |
| 21 | + headless ? `--headless=new` : '', |
| 22 | + `--disable-extensions-except=${pathToExtension}`, |
| 23 | + `--load-extension=${pathToExtension}`, |
| 24 | + '--no-first-run', // Disable Chrome's native first run experience. |
| 25 | + '--disable-client-side-phishing-detection', // Disables client-side phishing detection |
| 26 | + '--disable-component-extensions-with-background-pages', // Disable some built-in extensions that aren't affected by '--disable-extensions' |
| 27 | + '--disable-default-apps', // Disable installation of default apps |
| 28 | + '--disable-features=InterestFeedContentSuggestions', // Disables the Discover feed on NTP |
| 29 | + '--disable-features=Translate', // Disables Chrome translation, both the manual option and the popup prompt when a page with differing language is detected. |
| 30 | + '--hide-scrollbars', // Hide scrollbars from screenshots. |
| 31 | + '--mute-audio', // Mute any audio |
| 32 | + '--no-default-browser-check', // Disable the default browser check, do not prompt to set it as such |
| 33 | + '--no-first-run', // Skip first run wizards |
| 34 | + '--ash-no-nudges', // Avoids blue bubble "user education" nudges (eg., "… give your browser a new look", Memory Saver) |
| 35 | + '--disable-search-engine-choice-screen', // Disable the 2023+ search engine choice screen |
| 36 | + '--disable-features=MediaRoute', // Avoid the startup dialog for `Do you want the application “Chromium.app” to accept incoming network connections?`. Also disables the Chrome Media Router which creates background networking activity to discover cast targets. A superset of disabling DialMediaRouteProvider. |
| 37 | + '--use-mock-keychain', // Use mock keychain on Mac to prevent the blocking permissions dialog about "Chrome wants to use your confidential information stored in your keychain" |
| 38 | + '--disable-background-networking', // Disable various background network services, including extension updating, safe browsing service, upgrade detector, translate, UMA |
| 39 | + '--disable-breakpad', // Disable crashdump collection (reporting is already disabled in Chromium) |
| 40 | + '--disable-component-update', // Don't update the browser 'components' listed at chrome://components/ |
| 41 | + '--disable-domain-reliability', // Disables Domain Reliability Monitoring, which tracks whether the browser has difficulty contacting Google-owned sites and uploads reports to Google. |
| 42 | + '--disable-features=AutofillServerCommunicatio', // Disables autofill server communication. This feature isn't disabled via other 'parent' flags. |
| 43 | + '--disable-features=CertificateTransparencyComponentUpdate', |
| 44 | + '--disable-sync', // Disable syncing to a Google account |
| 45 | + '--disable-features=OptimizationHints', // Used for turning on Breakpad crash reporting in a debug environment where crash reporting is typically compiled but disabled. Disable the Chrome Optimization Guide and networking with its service API |
| 46 | + '--disable-features=DialMediaRouteProvider', // A weaker form of disabling the MediaRouter feature. See that flag's details. |
| 47 | + '--no-pings', // Don't send hyperlink auditing pings |
| 48 | + '--enable-features=SidePanelUpdates' // Ensure the side panel is visible. This is used for testing the side panel feature. |
| 49 | + ].filter((arg) => !!arg) |
| 50 | + }) |
| 51 | + await use(context) |
| 52 | + await context.close() |
| 53 | + }, |
| 54 | + extensionId: async ({context}, use) => { |
| 55 | + /* |
| 56 | + // for manifest v2: |
| 57 | + let [background] = context.backgroundPages() |
| 58 | + if (!background) |
| 59 | + background = await context.waitForEvent('backgroundpage') |
| 60 | + */ |
| 61 | + |
| 62 | + // for manifest v3: |
| 63 | + let [background] = context.serviceWorkers() |
| 64 | + if (!background) background = await context.waitForEvent('serviceworker') |
| 65 | + |
| 66 | + const extensionId = background.url().split('/')[2] |
| 67 | + await use(extensionId) |
| 68 | + } |
| 69 | + }) |
| 70 | +} |
| 71 | + |
| 72 | +// Screenshot function |
| 73 | +export async function takeScreenshot(page: any, screenshotPath: string) { |
| 74 | + await page.screenshot({path: screenshotPath}) |
| 75 | +} |
| 76 | + |
| 77 | +/** |
| 78 | + * Utility to access elements inside the Shadow DOM. |
| 79 | + * @param page The Playwright Page object. |
| 80 | + * @param shadowHostSelector The selector for the Shadow DOM host element. |
| 81 | + * @param innerSelector The selector for the element inside the Shadow DOM. |
| 82 | + * @returns A Promise resolving to an ElementHandle for the inner element or null if not found. |
| 83 | + */ |
| 84 | +export async function getShadowRootElement( |
| 85 | + page: Page, |
| 86 | + shadowHostSelector: string, |
| 87 | + innerSelector: string |
| 88 | +): Promise<ElementHandle<HTMLElement> | null> { |
| 89 | + // Wait for shadow host to be present first |
| 90 | + await page.waitForSelector(shadowHostSelector, {timeout: 15000}) |
| 91 | + |
| 92 | + // Get the shadow host element |
| 93 | + const shadowHost = await page.$(shadowHostSelector) |
| 94 | + if (!shadowHost) return null |
| 95 | + |
| 96 | + // Get its shadow root |
| 97 | + const shadowRoot = await page.evaluateHandle( |
| 98 | + (host) => host.shadowRoot, |
| 99 | + shadowHost |
| 100 | + ) |
| 101 | + if (!shadowRoot) return null |
| 102 | + |
| 103 | + // Find element within shadow root |
| 104 | + const element = await page.evaluateHandle( |
| 105 | + (root) => root?.querySelector(innerSelector), |
| 106 | + shadowRoot |
| 107 | + ) |
| 108 | + |
| 109 | + return element.asElement() as ElementHandle<HTMLElement> | null |
| 110 | +} |
0 commit comments