diff --git a/packages/playwright-test/src/mount.ts b/packages/playwright-test/src/mount.ts index 393359ecd8..372acd2fb6 100644 --- a/packages/playwright-test/src/mount.ts +++ b/packages/playwright-test/src/mount.ts @@ -21,51 +21,47 @@ let boundCallbacksForMount: Function[] = []; export const fixtures: Fixtures< PlaywrightTestArgs & PlaywrightTestOptions & { mount: (component: any, options: any) => Promise }, - PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _ctWorker: { page: Page | undefined, context: BrowserContext | undefined, hash: string, isolateTests: boolean } }, + PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _ctWorker: { context: BrowserContext | undefined, hash: string } }, { _contextFactory: (options?: BrowserContextOptions) => Promise }> = { - _ctWorker: [{ page: undefined, context: undefined, hash: '', isolateTests: false }, { scope: 'worker' }], - - context: async ({ _contextFactory, playwright, browser, _ctWorker, video, trace, viewport }, use, testInfo) => { - _ctWorker.isolateTests = shouldCaptureVideo(normalizeVideoMode(video), testInfo) || shouldCaptureTrace(normalizeTraceMode(trace), testInfo); - if (_ctWorker.isolateTests) { - await use(await _contextFactory()); - return; - } + _ctWorker: [{ context: undefined, hash: '' }, { scope: 'worker' }], + context: async ({ playwright, browser, _ctWorker, _contextFactory, video, trace }, use, testInfo) => { + const isolateTests = shouldCaptureVideo(normalizeVideoMode(video), testInfo) || shouldCaptureTrace(normalizeTraceMode(trace), testInfo); const defaultContextOptions = (playwright.chromium as any)._defaultContextOptions as BrowserContextOptions; const hash = contextHash(defaultContextOptions); - if (!_ctWorker.page || _ctWorker.hash !== hash) { + if (!_ctWorker.context || _ctWorker.hash !== hash || isolateTests) { if (_ctWorker.context) await _ctWorker.context.close(); - - const context = await browser.newContext(); - const page = await createPage(context); - _ctWorker.context = context; - _ctWorker.page = page; + // Context factory sets up video so we want to use that for isolated contexts. + // However, it closes the context after the test, so we don't want to use it + // for shared contexts. + _ctWorker.context = isolateTests ? await _contextFactory() : await browser.newContext(); _ctWorker.hash = hash; - await use(page.context()); - return; + await _ctWorker.context.addInitScript('navigator.serviceWorker.register = () => {}'); + await _ctWorker.context.exposeFunction('__pw_dispatch', (ordinal: number, args: any[]) => { + boundCallbacksForMount[ordinal](...args); + }); } else { - const page = _ctWorker.page; - await (page as any)._wrapApiCall(async () => { - await (page as any)._resetForReuse(); - await (page.context() as any)._resetForReuse(); - await page.goto('about:blank'); - await page.setViewportSize(viewport || { width: 1280, height: 800 }); - await page.goto(process.env.PLAYWRIGHT_VITE_COMPONENTS_BASE_URL!); - }, true); - await use(page.context()); + await (_ctWorker.context as any)._resetForReuse(); } + await use(_ctWorker.context); }, - page: async ({ context, _ctWorker }, use) => { - if (_ctWorker.isolateTests) { - await use(await createPage(context)); - return; - } - await use(_ctWorker.page!); + page: async ({ context, viewport }, use) => { + let page = context.pages()[0]; + await (context as any)._wrapApiCall(async () => { + if (!page) { + page = await context.newPage(); + } else { + await (page as any)._resetForReuse(); + await page.goto('about:blank'); + await page.setViewportSize(viewport || { width: 1280, height: 800 }); + } + await page.goto(process.env.PLAYWRIGHT_VITE_COMPONENTS_BASE_URL!); + }, true); + await use(page); }, mount: async ({ page }, use) => { @@ -148,15 +144,3 @@ function contextHash(context: BrowserContextOptions): string { }; return JSON.stringify(hash); } - -function createPage(context: BrowserContext): Promise { - return (context as any)._wrapApiCall(async () => { - const page = await context.newPage(); - await page.addInitScript('navigator.serviceWorker.register = () => {}'); - await page.exposeFunction('__pw_dispatch', (ordinal: number, args: any[]) => { - boundCallbacksForMount[ordinal](...args); - }); - await page.goto(process.env.PLAYWRIGHT_VITE_COMPONENTS_BASE_URL!); - return page; - }, true); -} diff --git a/tests/playwright-test/playwright.ct-reuse.spec.ts b/tests/playwright-test/playwright.ct-reuse.spec.ts index 7c1721bac8..e477851a07 100644 --- a/tests/playwright-test/playwright.ct-reuse.spec.ts +++ b/tests/playwright-test/playwright.ct-reuse.spec.ts @@ -111,3 +111,47 @@ test('should not reuse context with trace', async ({ runInlineTest }) => { expect(result.exitCode).toBe(0); expect(result.passed).toBe(2); }); + +test('should work with manually closed pages', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright/index.html': ``, + 'playwright/index.ts': ` + //@no-header + `, + + 'src/button.test.tsx': ` + //@no-header + import { test, expect } from '@playwright/experimental-ct-react'; + + test('closes page', async ({ mount, page }) => { + let hadEvent = false; + const component = await mount(); + await expect(component).toHaveText('Submit'); + await component.click(); + expect(hadEvent).toBe(true); + await page.close(); + }); + + test('creates a new page', async ({ mount, page, context }) => { + let hadEvent = false; + const component = await mount(); + await expect(component).toHaveText('Submit'); + await component.click(); + expect(hadEvent).toBe(true); + await page.close(); + await context.newPage(); + }); + + test('still works', async ({ mount }) => { + let hadEvent = false; + const component = await mount(); + await expect(component).toHaveText('Submit'); + await component.click(); + expect(hadEvent).toBe(true); + }); + `, + }, { workers: 1 }); + + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(3); +});