chore: create artifacts dir in the test runner (#26594)
This commit is contained in:
parent
fe7b956c3b
commit
1ceaa923ea
|
|
@ -62,6 +62,7 @@ export type WorkerInitParams = {
|
||||||
repeatEachIndex: number;
|
repeatEachIndex: number;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
config: SerializedConfig;
|
config: SerializedConfig;
|
||||||
|
artifactsDir: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TestBeginPayload = {
|
export type TestBeginPayload = {
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,11 @@ import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { APIRequestContext, BrowserContext, Browser, BrowserContextOptions, LaunchOptions, Page, Tracing, Video } from 'playwright-core';
|
import type { APIRequestContext, BrowserContext, Browser, BrowserContextOptions, LaunchOptions, Page, Tracing, Video } from 'playwright-core';
|
||||||
import * as playwrightLibrary 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 { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, TraceMode, VideoMode } from '../types/test';
|
||||||
import type { TestInfoImpl } from './worker/testInfo';
|
import type { TestInfoImpl } from './worker/testInfo';
|
||||||
import { rootTestType } from './common/testType';
|
import { rootTestType } from './common/testType';
|
||||||
import type { ContextReuseMode } from './common/config';
|
import type { ContextReuseMode } from './common/config';
|
||||||
import { artifactsFolderName } from './isomorphic/folders';
|
|
||||||
import type { ClientInstrumentation, ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation';
|
import type { ClientInstrumentation, ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation';
|
||||||
import type { ParsedStackTrace } from '../../playwright-core/src/utils/stackTrace';
|
import type { ParsedStackTrace } from '../../playwright-core/src/utils/stackTrace';
|
||||||
import { currentTestInfo } from './common/globals';
|
import { currentTestInfo } from './common/globals';
|
||||||
|
|
@ -55,7 +54,7 @@ type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
|
||||||
};
|
};
|
||||||
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
|
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
|
||||||
_browserOptions: LaunchOptions;
|
_browserOptions: LaunchOptions;
|
||||||
_artifactsDir: () => string;
|
_artifactsDir: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
|
|
@ -78,17 +77,8 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
video: ['off', { scope: 'worker', option: true }],
|
video: ['off', { scope: 'worker', option: true }],
|
||||||
trace: ['off', { scope: 'worker', option: true }],
|
trace: ['off', { scope: 'worker', option: true }],
|
||||||
|
|
||||||
_artifactsDir: [async ({}, use, workerInfo) => {
|
_artifactsDir: [async ({}, use) => {
|
||||||
let dir: string | undefined;
|
await use(process.env.TEST_ARTIFACTS_DIR!);
|
||||||
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]);
|
|
||||||
}, { scope: 'worker', _title: 'playwright configuration' } as any],
|
}, { scope: 'worker', _title: 'playwright configuration' } as any],
|
||||||
|
|
||||||
_browserOptions: [async ({ playwright, headless, channel, launchOptions, connectOptions, _artifactsDir }, use) => {
|
_browserOptions: [async ({ playwright, headless, channel, launchOptions, connectOptions, _artifactsDir }, use) => {
|
||||||
|
|
@ -100,7 +90,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
options.headless = headless;
|
options.headless = headless;
|
||||||
if (channel !== undefined)
|
if (channel !== undefined)
|
||||||
options.channel = channel;
|
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]) {
|
for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) {
|
||||||
(browserType as any)._defaultLaunchOptions = options;
|
(browserType as any)._defaultLaunchOptions = options;
|
||||||
|
|
@ -256,7 +246,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
(browserType as any)._defaultContextNavigationTimeout = navigationTimeout || 0;
|
(browserType as any)._defaultContextNavigationTimeout = navigationTimeout || 0;
|
||||||
}
|
}
|
||||||
(playwright.request as any)._defaultContextOptions = { ..._combinedContextOptions };
|
(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;
|
(playwright.request as any)._defaultContextOptions.timeout = actionTimeout || 0;
|
||||||
await use();
|
await use();
|
||||||
(playwright.request as any)._defaultContextOptions = undefined;
|
(playwright.request as any)._defaultContextOptions = undefined;
|
||||||
|
|
@ -268,7 +258,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
}, { auto: 'all-hooks-included', _title: 'context configuration' } as any],
|
}, { auto: 'all-hooks-included', _title: 'context configuration' } as any],
|
||||||
|
|
||||||
_setupArtifacts: [async ({ playwright, _artifactsDir, trace, screenshot }, use, testInfo) => {
|
_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);
|
await artifactsRecorder.willStartTest(testInfo as TestInfoImpl);
|
||||||
const csiListener: ClientInstrumentationListener = {
|
const csiListener: ClientInstrumentationListener = {
|
||||||
onApiCallBegin: (apiName: string, params: Record<string, any>, stackTrace: ParsedStackTrace | null, wallTime: number, userData: any) => {
|
onApiCallBegin: (apiName: string, params: Record<string, any>, stackTrace: ParsedStackTrace | null, wallTime: number, userData: any) => {
|
||||||
|
|
@ -337,7 +327,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
}
|
}
|
||||||
const videoOptions: BrowserContextOptions = captureVideo ? {
|
const videoOptions: BrowserContextOptions = captureVideo ? {
|
||||||
recordVideo: {
|
recordVideo: {
|
||||||
dir: _artifactsDir(),
|
dir: _artifactsDir,
|
||||||
size: typeof video === 'string' ? undefined : video.size,
|
size: typeof video === 'string' ? undefined : video.size,
|
||||||
}
|
}
|
||||||
} : {};
|
} : {};
|
||||||
|
|
|
||||||
|
|
@ -464,7 +464,9 @@ export class Dispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
_createWorker(testGroup: TestGroup, parallelIndex: number, loaderData: SerializedConfig) {
|
_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 handleOutput = (params: TestOutputPayload) => {
|
||||||
const chunk = chunkFromParams(params);
|
const chunk = chunkFromParams(params);
|
||||||
if (worker.didFail()) {
|
if (worker.didFail()) {
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
import type { TestGroup } from './testGroups';
|
import type { TestGroup } from './testGroups';
|
||||||
import type { RunPayload, SerializedConfig, WorkerInitParams } from '../common/ipc';
|
import type { RunPayload, SerializedConfig, WorkerInitParams } from '../common/ipc';
|
||||||
import { ProcessHost } from './processHost';
|
import { ProcessHost } from './processHost';
|
||||||
|
import { artifactsFolderName } from '../isomorphic/folders';
|
||||||
|
import { removeFolders } from 'playwright-core/lib/utils';
|
||||||
|
|
||||||
let lastWorkerIndex = 0;
|
let lastWorkerIndex = 0;
|
||||||
|
|
||||||
|
|
@ -27,7 +31,7 @@ export class WorkerHost extends ProcessHost {
|
||||||
currentTestId: string | null = null;
|
currentTestId: string | null = null;
|
||||||
private _params: WorkerInitParams;
|
private _params: WorkerInitParams;
|
||||||
|
|
||||||
constructor(testGroup: TestGroup, parallelIndex: number, config: SerializedConfig, extraEnv: Record<string, string | undefined>) {
|
constructor(testGroup: TestGroup, parallelIndex: number, config: SerializedConfig, extraEnv: Record<string, string | undefined>, outputDir: string) {
|
||||||
const workerIndex = lastWorkerIndex++;
|
const workerIndex = lastWorkerIndex++;
|
||||||
super(require.resolve('../worker/workerMain.js'), `worker-${workerIndex}`, {
|
super(require.resolve('../worker/workerMain.js'), `worker-${workerIndex}`, {
|
||||||
...extraEnv,
|
...extraEnv,
|
||||||
|
|
@ -44,13 +48,20 @@ export class WorkerHost extends ProcessHost {
|
||||||
repeatEachIndex: testGroup.repeatEachIndex,
|
repeatEachIndex: testGroup.repeatEachIndex,
|
||||||
projectId: testGroup.projectId,
|
projectId: testGroup.projectId,
|
||||||
config,
|
config,
|
||||||
|
artifactsDir: path.join(outputDir, artifactsFolderName(workerIndex))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
|
await fs.promises.mkdir(this._params.artifactsDir, { recursive: true });
|
||||||
await this.startRunner(this._params, false);
|
await this.startRunner(this._params, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override async stop(didFail?: boolean) {
|
||||||
|
await super.stop(didFail);
|
||||||
|
await removeFolders([this._params.artifactsDir]);
|
||||||
|
}
|
||||||
|
|
||||||
runTestGroup(runPayload: RunPayload) {
|
runTestGroup(runPayload: RunPayload) {
|
||||||
this.sendMessageNoReply({ method: 'runTestGroup', params: runPayload });
|
this.sendMessageNoReply({ method: 'runTestGroup', params: runPayload });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ export class WorkerMain extends ProcessRunner {
|
||||||
super();
|
super();
|
||||||
process.env.TEST_WORKER_INDEX = String(params.workerIndex);
|
process.env.TEST_WORKER_INDEX = String(params.workerIndex);
|
||||||
process.env.TEST_PARALLEL_INDEX = String(params.parallelIndex);
|
process.env.TEST_PARALLEL_INDEX = String(params.parallelIndex);
|
||||||
|
process.env.TEST_ARTIFACTS_DIR = params.artifactsDir;
|
||||||
setIsWorkerProcess();
|
setIsWorkerProcess();
|
||||||
|
|
||||||
this._params = params;
|
this._params = params;
|
||||||
|
|
|
||||||
|
|
@ -801,28 +801,3 @@ test('should use actionTimeout for APIRequestContext', async ({ runInlineTest, s
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(3);
|
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.');
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// Override locale fixture to check in teardown that no temporary trace zip was created.
|
||||||
locale: [async ({ locale, _artifactsDir }, use) => {
|
locale: [async ({ locale, _artifactsDir }, use) => {
|
||||||
await use(locale);
|
await use(locale);
|
||||||
const entries = fs.readdirSync(_artifactsDir());
|
const entries = fs.readdirSync(_artifactsDir);
|
||||||
expect(entries.filter(e => e.endsWith('.zip'))).toEqual([]);
|
expect(entries.filter(e => e.endsWith('.zip'))).toEqual([]);
|
||||||
}, { option: true }],
|
}, { 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.
|
// Override locale fixture to check in teardown that no temporary trace zip was created.
|
||||||
locale: [async ({ locale, _artifactsDir }, use) => {
|
locale: [async ({ locale, _artifactsDir }, use) => {
|
||||||
await use(locale);
|
await use(locale);
|
||||||
const entries = fs.readdirSync(_artifactsDir());
|
const entries = fs.readdirSync(_artifactsDir);
|
||||||
expect(entries.filter(e => e.endsWith('.zip'))).toEqual([]);
|
expect(entries.filter(e => e.endsWith('.zip'))).toEqual([]);
|
||||||
}, { option: true }],
|
}, { option: true }],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue