v1.1.0: pooled runtime, 959 tests, production hardening (0 squash)
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Tests for the PagePool module.
|
||||
*
|
||||
* Uses mock BrowserContext and Page objects to verify pooling,
|
||||
* borrowing, returning, resetting, and disposal behavior.
|
||||
*/
|
||||
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert'
|
||||
import type { Page, BrowserContext } from 'playwright'
|
||||
import { PagePool, PageEntry, ContextPool } from './page-pool.js'
|
||||
|
||||
function createMockContext(): BrowserContext & Record<string, any> {
|
||||
const context: any = {
|
||||
newPageCalls: 0,
|
||||
clearCookiesCalls: 0,
|
||||
closeCalls: 0,
|
||||
createdPages: [],
|
||||
newPage: async () => {
|
||||
context.newPageCalls++
|
||||
const page = createMockPage(context)
|
||||
context.createdPages.push(page)
|
||||
return page
|
||||
},
|
||||
clearCookies: async () => {
|
||||
context.clearCookiesCalls++
|
||||
},
|
||||
close: async () => {
|
||||
context.closeCalls++
|
||||
},
|
||||
}
|
||||
return context as BrowserContext & Record<string, any>
|
||||
}
|
||||
|
||||
function createMockPage(context?: any): Page & Record<string, any> {
|
||||
const page: any = {
|
||||
gotoCalls: [],
|
||||
evaluateCalls: 0,
|
||||
setViewportSizeCalls: [],
|
||||
closeCalls: 0,
|
||||
goto: async (url: string) => {
|
||||
page.gotoCalls.push(url)
|
||||
},
|
||||
context: () => context,
|
||||
evaluate: async () => {
|
||||
page.evaluateCalls++
|
||||
},
|
||||
setViewportSize: async (size: any) => {
|
||||
page.setViewportSizeCalls.push(size)
|
||||
},
|
||||
close: async () => {
|
||||
page.closeCalls++
|
||||
},
|
||||
addInitScript: async () => {},
|
||||
}
|
||||
return page as Page & Record<string, any>
|
||||
}
|
||||
|
||||
describe('PagePool', () => {
|
||||
it('warm creates specified number of pages', async () => {
|
||||
const context = createMockContext()
|
||||
const pool = new PagePool({} as ContextPool)
|
||||
|
||||
await pool.warm(context, 3)
|
||||
|
||||
assert.strictEqual(context.newPageCalls, 3)
|
||||
|
||||
const entries: PageEntry[] = (pool as any).pools.get(context) ?? []
|
||||
assert.strictEqual(entries.length, 3)
|
||||
assert.strictEqual(entries.every((e) => !e.inUse), true)
|
||||
})
|
||||
|
||||
it('borrow returns warmed page', async () => {
|
||||
const context = createMockContext()
|
||||
const pool = new PagePool({} as ContextPool)
|
||||
|
||||
await pool.warm(context, 1)
|
||||
const entriesBefore: PageEntry[] = (pool as any).pools.get(context) ?? []
|
||||
const warmedPage = entriesBefore[0].imhotepPage
|
||||
|
||||
const borrowed = await pool.borrow(context)
|
||||
|
||||
assert.strictEqual(borrowed, warmedPage)
|
||||
|
||||
const entriesAfter: PageEntry[] = (pool as any).pools.get(context) ?? []
|
||||
assert.strictEqual(entriesAfter[0].inUse, true)
|
||||
})
|
||||
|
||||
it('borrow creates new page when none available', async () => {
|
||||
const context = createMockContext()
|
||||
const pool = new PagePool({} as ContextPool)
|
||||
|
||||
const borrowed = await pool.borrow(context)
|
||||
|
||||
assert.strictEqual(context.newPageCalls, 1)
|
||||
assert.ok(borrowed)
|
||||
|
||||
const entries: PageEntry[] = (pool as any).pools.get(context) ?? []
|
||||
assert.strictEqual(entries.length, 1)
|
||||
assert.strictEqual(entries[0].inUse, true)
|
||||
})
|
||||
|
||||
it('borrow throws when at maxPagesPerContext and all in use', async () => {
|
||||
const context = createMockContext()
|
||||
const pool = new PagePool({} as ContextPool, { maxPagesPerContext: 2 })
|
||||
|
||||
await pool.borrow(context)
|
||||
await pool.borrow(context)
|
||||
|
||||
await assert.rejects(
|
||||
async () => pool.borrow(context),
|
||||
/Max pages per context \(2\) reached and all pages are in use/
|
||||
)
|
||||
})
|
||||
|
||||
it('return resets page and marks available', async () => {
|
||||
const context = createMockContext()
|
||||
const pool = new PagePool({} as ContextPool)
|
||||
|
||||
const borrowed = await pool.borrow(context)
|
||||
const mockPage = context.createdPages[0]
|
||||
|
||||
// Reset was called once during borrow
|
||||
assert.strictEqual(mockPage.evaluateCalls, 1)
|
||||
|
||||
await pool.return(borrowed)
|
||||
|
||||
// Return called reset again
|
||||
assert.strictEqual(mockPage.evaluateCalls, 2)
|
||||
|
||||
const entries: PageEntry[] = (pool as any).pools.get(context) ?? []
|
||||
assert.strictEqual(entries[0].inUse, false)
|
||||
})
|
||||
|
||||
it('reset clears cookies and localStorage', async () => {
|
||||
const context = createMockContext()
|
||||
const pool = new PagePool({} as ContextPool)
|
||||
|
||||
await pool.borrow(context)
|
||||
const mockPage = context.createdPages[0]
|
||||
|
||||
assert.strictEqual(mockPage.gotoCalls.includes('about:blank'), true)
|
||||
assert.strictEqual(context.clearCookiesCalls, 1)
|
||||
assert.strictEqual(mockPage.evaluateCalls, 1)
|
||||
assert.deepStrictEqual(mockPage.setViewportSizeCalls, [{ width: 1280, height: 720 }])
|
||||
})
|
||||
|
||||
it('dispose closes all pages', async () => {
|
||||
const context = createMockContext()
|
||||
const pool = new PagePool({} as ContextPool)
|
||||
|
||||
await pool.warm(context, 2)
|
||||
await pool.dispose()
|
||||
|
||||
assert.strictEqual(context.createdPages[0].closeCalls, 1)
|
||||
assert.strictEqual(context.createdPages[1].closeCalls, 1)
|
||||
})
|
||||
|
||||
it('idempotent dispose', async () => {
|
||||
const context = createMockContext()
|
||||
const pool = new PagePool({} as ContextPool)
|
||||
|
||||
await pool.warm(context, 1)
|
||||
await pool.dispose()
|
||||
await pool.dispose()
|
||||
|
||||
assert.strictEqual(context.createdPages[0].closeCalls, 1)
|
||||
})
|
||||
|
||||
it('borrowed page is inUse', async () => {
|
||||
const context = createMockContext()
|
||||
const pool = new PagePool({} as ContextPool, { maxPagesPerContext: 1 })
|
||||
|
||||
await pool.warm(context, 1)
|
||||
await pool.borrow(context)
|
||||
|
||||
await assert.rejects(
|
||||
async () => pool.borrow(context),
|
||||
/Max pages per context \(1\) reached and all pages are in use/
|
||||
)
|
||||
})
|
||||
|
||||
it('returned page is not inUse', async () => {
|
||||
const context = createMockContext()
|
||||
const pool = new PagePool({} as ContextPool, { maxPagesPerContext: 1 })
|
||||
|
||||
await pool.warm(context, 1)
|
||||
const first = await pool.borrow(context)
|
||||
await pool.return(first)
|
||||
|
||||
const second = await pool.borrow(context)
|
||||
|
||||
assert.strictEqual(context.newPageCalls, 1)
|
||||
assert.strictEqual(second, first)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user