diff --git a/packages/playwright-test/src/common/ipc.ts b/packages/playwright-test/src/common/ipc.ts index 5536426d56..0e5113e0eb 100644 --- a/packages/playwright-test/src/common/ipc.ts +++ b/packages/playwright-test/src/common/ipc.ts @@ -62,6 +62,7 @@ export type WorkerInitParams = { repeatEachIndex: number; projectId: string; config: SerializedConfig; + artifactsDir: string; }; export type TestBeginPayload = { diff --git a/packages/playwright-test/src/index.ts b/packages/playwright-test/src/index.ts index 10d5cc2676..72bdb379f6 100644 --- a/packages/playwright-test/src/index.ts +++ b/packages/playwright-test/src/index.ts @@ -18,12 +18,11 @@ import * as fs from 'fs'; import * as path from 'path'; import type { APIRequestContext, BrowserContext, Browser, BrowserContextOptions, LaunchOptions, Page, Tracing, Video } from 'playwright-core'; import * as playwrightLibrary from 'playwright-core'; -import { createGuid, debugMode, addInternalStackPrefix, mergeTraceFiles, saveTraceFile, removeFolders, isString, asLocator, jsonStringifyForceASCII } from 'playwright-core/lib/utils'; +import { createGuid, debugMode, addInternalStackPrefix, mergeTraceFiles, saveTraceFile, isString, asLocator, jsonStringifyForceASCII } from 'playwright-core/lib/utils'; import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, TraceMode, VideoMode } from '../types/test'; import type { TestInfoImpl } from './worker/testInfo'; import { rootTestType } from './common/testType'; import type { ContextReuseMode } from './common/config'; -import { artifactsFolderName } from './isomorphic/folders'; import type { ClientInstrumentation, ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation'; import type { ParsedStackTrace } from '../../playwright-core/src/utils/stackTrace'; import { currentTestInfo } from './common/globals'; @@ -55,7 +54,7 @@ type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & { }; type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _browserOptions: LaunchOptions; - _artifactsDir: () => string; + _artifactsDir: string; }; const playwrightFixtures: Fixtures = ({ @@ -78,17 +77,8 @@ const playwrightFixtures: Fixtures = ({ video: ['off', { scope: 'worker', option: true }], trace: ['off', { scope: 'worker', option: true }], - _artifactsDir: [async ({}, use, workerInfo) => { - let dir: string | undefined; - await use(() => { - if (!dir) { - dir = path.join(workerInfo.project.outputDir, artifactsFolderName(workerInfo.workerIndex)); - fs.mkdirSync(dir, { recursive: true }); - } - return dir; - }); - if (dir) - await removeFolders([dir]); + _artifactsDir: [async ({}, use) => { + await use(process.env.TEST_ARTIFACTS_DIR!); }, { scope: 'worker', _title: 'playwright configuration' } as any], _browserOptions: [async ({ playwright, headless, channel, launchOptions, connectOptions, _artifactsDir }, use) => { @@ -100,7 +90,7 @@ const playwrightFixtures: Fixtures = ({ options.headless = headless; if (channel !== undefined) options.channel = channel; - options.tracesDir = path.join(_artifactsDir(), 'traces'); + options.tracesDir = path.join(_artifactsDir, 'traces'); for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) { (browserType as any)._defaultLaunchOptions = options; @@ -256,7 +246,7 @@ const playwrightFixtures: Fixtures = ({ (browserType as any)._defaultContextNavigationTimeout = navigationTimeout || 0; } (playwright.request as any)._defaultContextOptions = { ..._combinedContextOptions }; - (playwright.request as any)._defaultContextOptions.tracesDir = path.join(_artifactsDir(), 'traces'); + (playwright.request as any)._defaultContextOptions.tracesDir = path.join(_artifactsDir, 'traces'); (playwright.request as any)._defaultContextOptions.timeout = actionTimeout || 0; await use(); (playwright.request as any)._defaultContextOptions = undefined; @@ -268,7 +258,7 @@ const playwrightFixtures: Fixtures = ({ }, { auto: 'all-hooks-included', _title: 'context configuration' } as any], _setupArtifacts: [async ({ playwright, _artifactsDir, trace, screenshot }, use, testInfo) => { - const artifactsRecorder = new ArtifactsRecorder(playwright, _artifactsDir(), trace, screenshot); + const artifactsRecorder = new ArtifactsRecorder(playwright, _artifactsDir, trace, screenshot); await artifactsRecorder.willStartTest(testInfo as TestInfoImpl); const csiListener: ClientInstrumentationListener = { onApiCallBegin: (apiName: string, params: Record, stackTrace: ParsedStackTrace | null, wallTime: number, userData: any) => { @@ -337,7 +327,7 @@ const playwrightFixtures: Fixtures = ({ } const videoOptions: BrowserContextOptions = captureVideo ? { recordVideo: { - dir: _artifactsDir(), + dir: _artifactsDir, size: typeof video === 'string' ? undefined : video.size, } } : {}; diff --git a/packages/playwright-test/src/runner/dispatcher.ts b/packages/playwright-test/src/runner/dispatcher.ts index 00544878e5..dcaf0f6289 100644 --- a/packages/playwright-test/src/runner/dispatcher.ts +++ b/packages/playwright-test/src/runner/dispatcher.ts @@ -464,7 +464,9 @@ export class Dispatcher { } _createWorker(testGroup: TestGroup, parallelIndex: number, loaderData: SerializedConfig) { - const worker = new WorkerHost(testGroup, parallelIndex, loaderData, this._extraEnvByProjectId.get(testGroup.projectId) || {}); + const projectConfig = this._config.projects.find(p => p.id === testGroup.projectId)!; + const outputDir = projectConfig.project.outputDir; + const worker = new WorkerHost(testGroup, parallelIndex, loaderData, this._extraEnvByProjectId.get(testGroup.projectId) || {}, outputDir); const handleOutput = (params: TestOutputPayload) => { const chunk = chunkFromParams(params); if (worker.didFail()) { diff --git a/packages/playwright-test/src/runner/workerHost.ts b/packages/playwright-test/src/runner/workerHost.ts index 14f09d7852..4a71a6af01 100644 --- a/packages/playwright-test/src/runner/workerHost.ts +++ b/packages/playwright-test/src/runner/workerHost.ts @@ -14,9 +14,13 @@ * limitations under the License. */ +import fs from 'fs'; +import path from 'path'; import type { TestGroup } from './testGroups'; import type { RunPayload, SerializedConfig, WorkerInitParams } from '../common/ipc'; import { ProcessHost } from './processHost'; +import { artifactsFolderName } from '../isomorphic/folders'; +import { removeFolders } from 'playwright-core/lib/utils'; let lastWorkerIndex = 0; @@ -27,7 +31,7 @@ export class WorkerHost extends ProcessHost { currentTestId: string | null = null; private _params: WorkerInitParams; - constructor(testGroup: TestGroup, parallelIndex: number, config: SerializedConfig, extraEnv: Record) { + constructor(testGroup: TestGroup, parallelIndex: number, config: SerializedConfig, extraEnv: Record, outputDir: string) { const workerIndex = lastWorkerIndex++; super(require.resolve('../worker/workerMain.js'), `worker-${workerIndex}`, { ...extraEnv, @@ -44,13 +48,20 @@ export class WorkerHost extends ProcessHost { repeatEachIndex: testGroup.repeatEachIndex, projectId: testGroup.projectId, config, + artifactsDir: path.join(outputDir, artifactsFolderName(workerIndex)) }; } async start() { + await fs.promises.mkdir(this._params.artifactsDir, { recursive: true }); await this.startRunner(this._params, false); } + override async stop(didFail?: boolean) { + await super.stop(didFail); + await removeFolders([this._params.artifactsDir]); + } + runTestGroup(runPayload: RunPayload) { this.sendMessageNoReply({ method: 'runTestGroup', params: runPayload }); } diff --git a/packages/playwright-test/src/worker/workerMain.ts b/packages/playwright-test/src/worker/workerMain.ts index 01937843cf..a54f845805 100644 --- a/packages/playwright-test/src/worker/workerMain.ts +++ b/packages/playwright-test/src/worker/workerMain.ts @@ -63,6 +63,7 @@ export class WorkerMain extends ProcessRunner { super(); process.env.TEST_WORKER_INDEX = String(params.workerIndex); process.env.TEST_PARALLEL_INDEX = String(params.parallelIndex); + process.env.TEST_ARTIFACTS_DIR = params.artifactsDir; setIsWorkerProcess(); this._params = params; diff --git a/tests/playwright-test/playwright.spec.ts b/tests/playwright-test/playwright.spec.ts index 154fa3f909..e77428b069 100644 --- a/tests/playwright-test/playwright.spec.ts +++ b/tests/playwright-test/playwright.spec.ts @@ -801,28 +801,3 @@ test('should use actionTimeout for APIRequestContext', async ({ runInlineTest, s expect(result.exitCode).toBe(0); expect(result.passed).toBe(3); }); - -test('should cancel apiRequests if test will timeout', async ({ runInlineTest, server }) => { - server.setRoute('/stall', (req, res) => {}); - const result = await runInlineTest({ - 'playwright.config.js': ` - module.exports = { - timeout: 1000, - use: { - baseURL: '${server.PREFIX}', - } - }; - `, - 'a.test.ts': ` - import { test, expect } from '@playwright/test'; - test('pass', async ({ request }) => { - await request.get('/stall') - }); - `, - }, { workers: 1 }); - expect(result.exitCode).toBe(1); - expect(result.passed).toBe(0); - expect(result.failed).toBe(1); - expect(result.output).toContain('apiRequestContext.get: Request context disposed.'); - expect(result.output).toContain('Test timeout of 1000ms exceeded.'); -}); diff --git a/tests/playwright-test/playwright.trace.spec.ts b/tests/playwright-test/playwright.trace.spec.ts index bd44e027dd..0fd48c517a 100644 --- a/tests/playwright-test/playwright.trace.spec.ts +++ b/tests/playwright-test/playwright.trace.spec.ts @@ -424,7 +424,7 @@ for (const mode of ['off', 'retain-on-failure', 'on-first-retry', 'on-all-retrie // Override locale fixture to check in teardown that no temporary trace zip was created. locale: [async ({ locale, _artifactsDir }, use) => { await use(locale); - const entries = fs.readdirSync(_artifactsDir()); + const entries = fs.readdirSync(_artifactsDir); expect(entries.filter(e => e.endsWith('.zip'))).toEqual([]); }, { option: true }], }); @@ -449,7 +449,7 @@ for (const mode of ['off', 'retain-on-failure', 'on-first-retry', 'on-all-retrie // Override locale fixture to check in teardown that no temporary trace zip was created. locale: [async ({ locale, _artifactsDir }, use) => { await use(locale); - const entries = fs.readdirSync(_artifactsDir()); + const entries = fs.readdirSync(_artifactsDir); expect(entries.filter(e => e.endsWith('.zip'))).toEqual([]); }, { option: true }], });