197 lines
5.6 KiB
TypeScript
197 lines
5.6 KiB
TypeScript
/**
|
|
* 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)
|
|
})
|
|
})
|