diff --git a/packages/playwright-test/src/index.ts b/packages/playwright-test/src/index.ts index 109824483d0bb..0da9779d0304f 100644 --- a/packages/playwright-test/src/index.ts +++ b/packages/playwright-test/src/index.ts @@ -24,6 +24,7 @@ import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWor import { store as _baseStore } from './store'; import type { TestInfoImpl } from './testInfo'; import { rootTestType, _setProjectSetup } from './testType'; +import { type ContextReuseMode } from './types'; export { expect } from './expect'; export { addRunnerPlugin as _addRunnerPlugin } from './plugins'; export const _baseTest: TestType<{}, {}> = rootTestType.test; @@ -43,7 +44,7 @@ if ((process as any)['__pw_initiator__']) { type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & { _combinedContextOptions: BrowserContextOptions, - _contextReuseEnabled: boolean, + _contextReuseMode: ContextReuseMode, _reuseContext: boolean, _setupContextOptionsAndArtifacts: void; _contextFactory: (options?: BrowserContextOptions) => Promise; @@ -238,7 +239,7 @@ const playwrightFixtures: Fixtures = ({ _snapshotSuffix: [process.platform, { scope: 'worker' }], - _setupContextOptionsAndArtifacts: [async ({ playwright, _snapshotSuffix, _combinedContextOptions, _browserOptions, _artifactsDir, trace, screenshot, actionTimeout, navigationTimeout, testIdAttribute }, use, testInfo) => { + _setupContextOptionsAndArtifacts: [async ({ playwright, _snapshotSuffix, _combinedContextOptions, _reuseContext, _artifactsDir, trace, screenshot, actionTimeout, navigationTimeout, testIdAttribute }, use, testInfo) => { if (testIdAttribute) playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute); testInfo.snapshotSuffix = _snapshotSuffix; @@ -250,7 +251,7 @@ const playwrightFixtures: Fixtures = ({ const traceMode = normalizeTraceMode(trace); const defaultTraceOptions = { screenshots: true, snapshots: true, sources: true }; const traceOptions = typeof trace === 'string' ? defaultTraceOptions : { ...defaultTraceOptions, ...trace, mode: undefined }; - const captureTrace = shouldCaptureTrace(traceMode, testInfo); + const captureTrace = shouldCaptureTrace(traceMode, testInfo) && !_reuseContext; const temporaryTraceFiles: string[] = []; const temporaryScreenshots: string[] = []; const testInfoImpl = testInfo as TestInfoImpl; @@ -462,9 +463,9 @@ const playwrightFixtures: Fixtures = ({ })); }, { auto: 'all-hooks-included', _title: 'playwright configuration' } as any], - _contextFactory: [async ({ browser, video, _artifactsDir }, use, testInfo) => { + _contextFactory: [async ({ browser, video, _artifactsDir, _reuseContext }, use, testInfo) => { const videoMode = normalizeVideoMode(video); - const captureVideo = shouldCaptureVideo(videoMode, testInfo); + const captureVideo = shouldCaptureVideo(videoMode, testInfo) && !_reuseContext; const contexts = new Map(); await use(async options => { @@ -518,10 +519,11 @@ const playwrightFixtures: Fixtures = ({ testInfo.errors.push({ message: prependToError }); }, { scope: 'test', _title: 'context' } as any], - _contextReuseEnabled: !!process.env.PW_TEST_REUSE_CONTEXT, + _contextReuseMode: process.env.PW_TEST_REUSE_CONTEXT === 'when-possible' ? 'when-possible' : (process.env.PW_TEST_REUSE_CONTEXT ? 'force' : 'none'), - _reuseContext: async ({ video, trace, _contextReuseEnabled }, use, testInfo) => { - const reuse = _contextReuseEnabled && !shouldCaptureVideo(normalizeVideoMode(video), testInfo) && !shouldCaptureTrace(normalizeTraceMode(trace), testInfo); + _reuseContext: async ({ video, trace, _contextReuseMode }, use, testInfo) => { + const reuse = _contextReuseMode === 'force' || + (_contextReuseMode === 'when-possible' && !shouldCaptureVideo(normalizeVideoMode(video), testInfo) && !shouldCaptureTrace(normalizeTraceMode(trace), testInfo)); await use(reuse); }, @@ -602,7 +604,7 @@ export function normalizeVideoMode(video: VideoMode | 'retry-with-video' | { mod return videoMode; } -export function shouldCaptureVideo(videoMode: VideoMode, testInfo: TestInfo) { +function shouldCaptureVideo(videoMode: VideoMode, testInfo: TestInfo) { return (videoMode === 'on' || videoMode === 'retain-on-failure' || (videoMode === 'on-first-retry' && testInfo.retry === 1)); } @@ -615,7 +617,7 @@ export function normalizeTraceMode(trace: TraceMode | 'retry-with-trace' | { mod return traceMode; } -export function shouldCaptureTrace(traceMode: TraceMode, testInfo: TestInfo) { +function shouldCaptureTrace(traceMode: TraceMode, testInfo: TestInfo) { return traceMode === 'on' || traceMode === 'retain-on-failure' || (traceMode === 'on-first-retry' && testInfo.retry === 1); } diff --git a/packages/playwright-test/src/mount.ts b/packages/playwright-test/src/mount.ts index 3d2176da924ae..ec1721c5833ed 100644 --- a/packages/playwright-test/src/mount.ts +++ b/packages/playwright-test/src/mount.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Fixtures, Locator, Page, BrowserContextOptions, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, BrowserContext } from './types'; +import type { Fixtures, Locator, Page, BrowserContextOptions, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, BrowserContext, ContextReuseMode } from './types'; import type { Component, JsxComponent, MountOptions } from '../types/component'; let boundCallbacksForMount: Function[] = []; @@ -29,9 +29,9 @@ export const fixtures: Fixtures< mount: (component: any, options: any) => Promise; }, PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _ctWorker: { context: BrowserContext | undefined, hash: string } }, - { _contextFactory: (options?: BrowserContextOptions) => Promise, _contextReuseEnabled: boolean }> = { + { _contextFactory: (options?: BrowserContextOptions) => Promise, _contextReuseMode: ContextReuseMode }> = { - _contextReuseEnabled: true, + _contextReuseMode: 'when-possible', serviceWorkers: 'block', diff --git a/packages/playwright-test/src/types.ts b/packages/playwright-test/src/types.ts index 62b612a3a381b..a261ae2551511 100644 --- a/packages/playwright-test/src/types.ts +++ b/packages/playwright-test/src/types.ts @@ -76,3 +76,5 @@ export interface FullProjectInternal extends FullProjectPublic { export interface ReporterInternal extends Reporter { _onExit?(): void | Promise; } + +export type ContextReuseMode = 'none' | 'force' | 'when-possible'; diff --git a/tests/playwright-test/playwright.reuse.spec.ts b/tests/playwright-test/playwright.reuse.spec.ts index 8017e99f44735..896a6ce4390b9 100644 --- a/tests/playwright-test/playwright.reuse.spec.ts +++ b/tests/playwright-test/playwright.reuse.spec.ts @@ -15,6 +15,7 @@ */ import { test, expect } from './playwright-test-fixtures'; +import fs from 'fs'; test('should reuse context', async ({ runInlineTest }) => { const result = await runInlineTest({ @@ -56,7 +57,7 @@ test('should reuse context', async ({ runInlineTest }) => { expect(result.passed).toBe(5); }); -test('should not reuse context with video', async ({ runInlineTest }) => { +test('should not reuse context with video if mode=when-possible', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ 'playwright.config.ts': ` export default { @@ -65,23 +66,54 @@ test('should not reuse context with video', async ({ runInlineTest }) => { `, 'src/reuse.test.ts': ` const { test } = pwt; - let lastContext; + let lastContextGuid; test('one', async ({ context }) => { - lastContext = context; + lastContextGuid = context._guid; }); test('two', async ({ context }) => { - expect(context).not.toBe(lastContext); + expect(context._guid).not.toBe(lastContextGuid); + }); + `, + }, { workers: 1 }, { PW_TEST_REUSE_CONTEXT: 'when-possible' }); + + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(2); + expect(fs.existsSync(testInfo.outputPath('test-results', 'reuse-one', 'video.webm'))).toBeFalsy(); + expect(fs.existsSync(testInfo.outputPath('test-results', 'reuse-two', 'video.webm'))).toBeFalsy(); +}); + +test('should reuse context and disable video if mode=force', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + export default { + use: { video: 'on' }, + }; + `, + 'reuse.test.ts': ` + const { test } = pwt; + let lastContextGuid; + + test('one', async ({ context, page }) => { + lastContextGuid = context._guid; + await page.waitForTimeout(2000); + }); + + test('two', async ({ context, page }) => { + expect(context._guid).toBe(lastContextGuid); + await page.waitForTimeout(2000); }); `, }, { workers: 1 }, { PW_TEST_REUSE_CONTEXT: '1' }); expect(result.exitCode).toBe(0); expect(result.passed).toBe(2); + expect(fs.existsSync(testInfo.outputPath('test-results', 'reuse-one', 'video.webm'))).toBeFalsy(); + expect(fs.existsSync(testInfo.outputPath('test-results', 'reuse-two', 'video.webm'))).toBeFalsy(); }); -test('should not reuse context with trace', async ({ runInlineTest }) => { +test('should not reuse context with trace if mode=when-possible', async ({ runInlineTest }) => { const result = await runInlineTest({ 'playwright.config.ts': ` export default { @@ -90,17 +122,17 @@ test('should not reuse context with trace', async ({ runInlineTest }) => { `, 'src/reuse.test.ts': ` const { test } = pwt; - let lastContext; + let lastContextGuid; test('one', async ({ context }) => { - lastContext = context; + lastContextGuid = context._guid; }); test('two', async ({ context }) => { - expect(context).not.toBe(lastContext); + expect(context._guid).not.toBe(lastContextGuid); }); `, - }, { workers: 1 }, { PW_TEST_REUSE_CONTEXT: '1' }); + }, { workers: 1 }, { PW_TEST_REUSE_CONTEXT: 'when-possible' }); expect(result.exitCode).toBe(0); expect(result.passed).toBe(2);