fix: preserve trace if context if closed manually during test run (#22442)

Reference #22122, #22120
This commit is contained in:
Yury Semikhatsky 2023-04-18 09:02:33 -07:00 committed by GitHub
parent d9e7a4fffd
commit 2ea214d6f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 75 additions and 7 deletions

View file

@ -325,13 +325,16 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
}; };
const startedCollectingArtifacts = Symbol('startedCollectingArtifacts'); const startedCollectingArtifacts = Symbol('startedCollectingArtifacts');
const stopTracing = async (tracing: Tracing) => { const stopTracing = async (tracing: Tracing, contextTearDownStarted: boolean) => {
if ((tracing as any)[startedCollectingArtifacts]) if ((tracing as any)[startedCollectingArtifacts])
return; return;
(tracing as any)[startedCollectingArtifacts] = true; (tracing as any)[startedCollectingArtifacts] = true;
if (captureTrace) { if (captureTrace) {
let tracePath; let tracePath;
if (preserveTrace()) { // Create a trace file if we know that:
// - it is's going to be used due to the config setting and the test status or
// - we are inside a test or afterEach and the user manually closed the context.
if (preserveTrace() || !contextTearDownStarted) {
tracePath = path.join(_artifactsDir(), createGuid() + '.zip'); tracePath = path.join(_artifactsDir(), createGuid() + '.zip');
temporaryTraceFiles.push(tracePath); temporaryTraceFiles.push(tracePath);
} }
@ -363,7 +366,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
// Do not record empty traces and useless screenshots for them. // Do not record empty traces and useless screenshots for them.
if (reusedContexts.has(context)) if (reusedContexts.has(context))
return; return;
await stopTracing(context.tracing); await stopTracing(context.tracing, (context as any)[kStartedContextTearDown]);
if (screenshotMode === 'on' || screenshotMode === 'only-on-failure') { if (screenshotMode === 'on' || screenshotMode === 'only-on-failure') {
// Capture screenshot for now. We'll know whether we have to preserve them // Capture screenshot for now. We'll know whether we have to preserve them
// after the test finishes. // after the test finishes.
@ -373,7 +376,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
const onWillCloseRequestContext = async (context: APIRequestContext) => { const onWillCloseRequestContext = async (context: APIRequestContext) => {
const tracing = (context as any)._tracing as Tracing; const tracing = (context as any)._tracing as Tracing;
await stopTracing(tracing); await stopTracing(tracing, (context as any)[kStartedContextTearDown]);
}; };
// 1. Setup instrumentation and process existing contexts. // 1. Setup instrumentation and process existing contexts.
@ -435,7 +438,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
// 5. Collect artifacts from any non-closed contexts. // 5. Collect artifacts from any non-closed contexts.
await Promise.all(leftoverContexts.map(async context => { await Promise.all(leftoverContexts.map(async context => {
await stopTracing(context.tracing); await stopTracing(context.tracing, true);
if (captureScreenshots) { if (captureScreenshots) {
await Promise.all(context.pages().map(async page => { await Promise.all(context.pages().map(async page => {
if ((page as any)[screenshottedSymbol]) if ((page as any)[screenshottedSymbol])
@ -447,7 +450,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
} }
}).concat(leftoverApiRequests.map(async context => { }).concat(leftoverApiRequests.map(async context => {
const tracing = (context as any)._tracing as Tracing; const tracing = (context as any)._tracing as Tracing;
await stopTracing(tracing); await stopTracing(tracing, true);
}))); })));
// 6. Save test trace. // 6. Save test trace.
@ -508,6 +511,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
let counter = 0; let counter = 0;
await Promise.all([...contexts.keys()].map(async context => { await Promise.all([...contexts.keys()].map(async context => {
(context as any)[kStartedContextTearDown] = true;
await context.close(); await context.close();
const testFailed = testInfo.status !== testInfo.expectedStatus; const testFailed = testInfo.status !== testInfo.expectedStatus;
@ -568,6 +572,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
request: async ({ playwright }, use) => { request: async ({ playwright }, use) => {
const request = await playwright.request.newContext(); const request = await playwright.request.newContext();
await use(request); await use(request);
(request as any)[kStartedContextTearDown] = true;
await request.dispose(); await request.dispose();
}, },
}); });
@ -657,6 +662,7 @@ function attachConnectedHeaderIfNeeded(testInfo: TestInfo, browser: Browser | nu
const kTracingStarted = Symbol('kTracingStarted'); const kTracingStarted = Symbol('kTracingStarted');
const kIsReusedContext = Symbol('kReusedContext'); const kIsReusedContext = Symbol('kReusedContext');
const kStartedContextTearDown = Symbol('kStartedContextTearDown');
function connectOptionsFromEnv() { function connectOptionsFromEnv() {
const wsEndpoint = process.env.PW_TEST_CONNECT_WS_ENDPOINT; const wsEndpoint = process.env.PW_TEST_CONNECT_WS_ENDPOINT;

View file

@ -392,3 +392,65 @@ for (const mode of ['off', 'retain-on-failure', 'on-first-retry', 'on-all-retrie
expect(result.passed).toBe(1); expect(result.passed).toBe(1);
}); });
} }
test(`trace:retain-on-failure should create trace if context is closed before failure in the test`, async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = { use: { trace: 'retain-on-failure' } };
`,
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('passing test', async ({ page, context }) => {
await page.goto('about:blank');
await context.close();
expect(1).toBe(2);
});
`,
}, { trace: 'retain-on-failure' });
const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.zip');
const trace = await parseTrace(tracePath);
expect(trace.actions).toContain('page.goto');
expect(result.failed).toBe(1);
});
test(`trace:retain-on-failure should create trace if context is closed before failure in afterEach`, async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = { use: { trace: 'retain-on-failure' } };
`,
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('passing test', async ({ page, context }) => {
});
test.afterEach(async ({ page, context }) => {
await page.goto('about:blank');
await context.close();
expect(1).toBe(2);
});
`,
}, { trace: 'retain-on-failure' });
const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.zip');
const trace = await parseTrace(tracePath);
expect(trace.actions).toContain('page.goto');
expect(result.failed).toBe(1);
});
test(`trace:retain-on-failure should create trace if request context is disposed before failure`, async ({ runInlineTest, server }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = { use: { trace: 'retain-on-failure' } };
`,
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('passing test', async ({ request }) => {
expect(await request.get('${server.EMPTY_PAGE}')).toBeOK();
await request.dispose();
expect(1).toBe(2);
});
`,
}, { trace: 'retain-on-failure' });
const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.zip');
const trace = await parseTrace(tracePath);
expect(trace.actions).toContain('apiRequestContext.get');
expect(result.failed).toBe(1);
});