doesitarm/test/playwright/apple-silicon-app-test.playwright.ts
ThatGuySam 0480c47bbb test(playwright): lock browser coverage before scanner refactors
Add a typed Playwright harness for Pagefind and the Apple Silicon app-test flow so scanner work has browser-level protection. Keep the rollout plan in the same stack so the TypeScript conversion stays staged and reviewable.

Constraint: Must not change production runtime behavior in this commit
Rejected: Leave the old JS browser test and add a second harness | duplicates setup and leaves the targeted browser script broken
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep browser-only helpers under test/playwright/support until the runtime scanner surface is fully typed
Tested: pnpm run typecheck; pnpm run test:browser; pnpm run test:browser:pagefind
Not-tested: Live browser checks against doesitarm.com
2026-04-04 14:55:13 -05:00

178 lines
5.5 KiB
TypeScript

import type { Browser, Page } from 'playwright-core'
import {
afterAll,
beforeAll,
describe,
expect,
it
} from 'vitest'
import {
launchBrowser,
startAstroDevServer,
stopChildProcess,
type AstroDevServer
} from './support/astro-browser-test'
import {
createNativeAppArchive,
type PlaywrightUploadFile
} from './support/app-archive-fixture'
const appTestVariants = [
{
name: 'legacy scanner',
routeSuffix: ''
},
{
name: 'worker scanner',
routeSuffix: '?version=2'
}
] as const
describe( 'Apple Silicon app test page', () => {
let browser: Browser
let devServer: AstroDevServer
let appArchive: PlaywrightUploadFile
beforeAll( async () => {
appArchive = await createNativeAppArchive()
devServer = await startAstroDevServer({
env: {
TEST_RESULT_STORE: '/api/test-results'
},
preferConfiguredBaseUrl: false
})
browser = await launchBrowser()
await Promise.all( appTestVariants.map( variant => {
return warmAppTestRoute( browser, devServer.baseUrl, variant.routeSuffix )
} ) )
} )
afterAll( async () => {
await browser?.close()
await stopChildProcess( devServer?.process || null )
} )
it.each( appTestVariants )( 'uploads an app archive through the %s path and renders a native result', async ( variant ) => {
const page = await browser.newPage()
const consoleErrors: string[] = []
const pageErrors: string[] = []
const submittedScans: Record<string, unknown>[] = []
page.on( 'console', message => {
if ( message.type() === 'error' ) {
consoleErrors.push( message.text() )
}
} )
page.on( 'pageerror', error => {
pageErrors.push( error.message )
} )
await stubResultStore( page, submittedScans )
await page.goto( `${ devServer.baseUrl }/apple-silicon-app-test/${ variant.routeSuffix }`, {
waitUntil: 'load'
} )
await page.waitForFunction( () => {
const island = document.querySelector( 'astro-island[component-url="/pages/apple-silicon-app-test.vue"]' )
return Boolean( island && !island.hasAttribute( 'ssr' ) )
}, {
timeout: 30 * 1000
} )
await page.locator( 'input[type="file"]' ).setInputFiles( appArchive )
await waitForBodyText( page, 'Total Files: 1', {
consoleErrors,
devServerOutput: devServer.output.text,
pageErrors
} )
const firstScanRow = page.locator( '.results-container li' ).first()
await waitForBodyText( page, 'Playwright Native App', {
consoleErrors,
devServerOutput: devServer.output.text,
pageErrors
} )
await waitForBodyText( page, '✅ This app is natively compatible with Apple Silicon!', {
consoleErrors,
devServerOutput: devServer.output.text,
pageErrors
} )
await firstScanRow.locator( 'summary' ).click()
const rowText = await firstScanRow.textContent()
expect( rowText ).toContain( 'Bundle Identifier' )
expect( rowText ).toContain( 'com.doesitarm.playwright-native-app' )
expect( submittedScans.length, devServer.output.text ).toBe( 1 )
expect( submittedScans[ 0 ]?.filename, JSON.stringify( submittedScans[ 0 ] ) ).toBe( 'Playwright Native App.app.zip' )
expect( submittedScans[ 0 ]?.result, JSON.stringify( submittedScans[ 0 ] ) ).toBe( '✅' )
expect( pageErrors, devServer.output.text ).toEqual( [] )
expect( consoleErrors, devServer.output.text ).toEqual( [] )
} )
} )
async function stubResultStore ( page: Page, submittedScans: Record<string, unknown>[] ) {
await page.route( '**/api/test-results', async route => {
const postData = route.request().postDataJSON()
if ( postData && typeof postData === 'object' ) {
submittedScans.push( postData as Record<string, unknown> )
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
supportedVersionNumber: null
})
})
} )
}
async function waitForBodyText ( page: Page, expectedText: string, debugContext: {
consoleErrors: string[]
devServerOutput: string
pageErrors: string[]
} ) {
try {
await page.waitForFunction( textToFind => {
return Boolean( document.body?.textContent?.includes( textToFind ) )
}, expectedText, {
timeout: 30 * 1000
} )
} catch ( error ) {
const bodyText = await page.locator( 'body' ).textContent()
throw new Error( [
`Timed out waiting for body text: ${ expectedText }`,
bodyText || '',
debugContext.pageErrors.join( '\n' ),
debugContext.consoleErrors.join( '\n' ),
debugContext.devServerOutput
].filter( Boolean ).join( '\n\n' ), {
cause: error
} )
}
}
async function warmAppTestRoute ( browser: Browser, baseUrl: string, routeSuffix = '' ) {
const warmPage = await browser.newPage()
try {
await warmPage.goto( `${ baseUrl }/apple-silicon-app-test/${ routeSuffix }`, {
waitUntil: 'load'
} )
await warmPage.waitForTimeout( 5000 )
} finally {
await warmPage.close()
}
}