Skip to content

Commit 934bc67

Browse files
authored
test(tracing): start adding tracing tests (#6369)
1 parent 9da718d commit 934bc67

File tree

6 files changed

+145
-5
lines changed

6 files changed

+145
-5
lines changed

src/server/snapshot/snapshotter.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ export class Snapshotter {
5252
this._snapshotStreamer = '__playwright_snapshot_streamer_' + guid;
5353
}
5454

55+
started(): boolean {
56+
return this._started;
57+
}
58+
5559
async start() {
5660
this._started = true;
5761
if (!this._initialized) {

src/server/trace/recorder/traceSnapshotter.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ export class TraceSnapshotter extends EventEmitter implements SnapshotterDelegat
4242
this._writeArtifactChain = Promise.resolve();
4343
}
4444

45+
started(): boolean {
46+
return this._snapshotter.started();
47+
}
48+
4549
async start(): Promise<void> {
4650
await this._snapshotter.start();
4751
}

src/server/trace/recorder/tracing.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,13 @@ export class Tracing implements InstrumentationListener {
118118
const zipFile = new yazl.ZipFile();
119119
zipFile.addFile(this._traceFile, 'trace.trace');
120120
const zipFileName = this._traceFile + '.zip';
121+
this._traceFile = undefined;
121122
for (const sha1 of this._sha1s)
122123
zipFile.addFile(path.join(this._resourcesDir!, sha1), path.join('resources', sha1));
123-
const zipPromise = new Promise(f => {
124+
zipFile.end();
125+
await new Promise(f => {
124126
zipFile.outputStream.pipe(fs.createWriteStream(zipFileName)).on('close', f);
125127
});
126-
zipFile.end();
127-
await zipPromise;
128128
const artifact = new Artifact(this._context, zipFileName);
129129
artifact.reportFinished();
130130
return artifact;
@@ -133,7 +133,7 @@ export class Tracing implements InstrumentationListener {
133133
async _captureSnapshot(name: 'before' | 'after' | 'action' | 'event', sdkObject: SdkObject, metadata: CallMetadata, element?: ElementHandle) {
134134
if (!sdkObject.attribution.page)
135135
return;
136-
if (!this._snapshotter)
136+
if (!this._snapshotter.started())
137137
return;
138138
const snapshotName = `${name}@${metadata.id}`;
139139
metadata.snapshots.push({ title: name, snapshotName });

tests/config/browserTest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,5 +209,6 @@ class ContextEnv {
209209
}
210210

211211
export const contextTest = browserTest.extend(new ContextEnv());
212+
export const tracingTest = baseTest.extend(new PlaywrightEnv()).extend(new BrowserEnv()).extend(new ContextEnv());
212213

213214
export { expect } from 'folio';

tests/config/default.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import * as folio from 'folio';
1818
import * as path from 'path';
19-
import { playwrightTest, slowPlaywrightTest, contextTest } from './browserTest';
19+
import { playwrightTest, slowPlaywrightTest, contextTest, tracingTest } from './browserTest';
2020
import { test as pageTest } from './pageTest';
2121
import { BrowserName, CommonTestArgs, CommonWorkerArgs } from './baseTest';
2222
import type { Browser, BrowserContext } from '../../index';
@@ -115,4 +115,5 @@ for (const browserName of browsers) {
115115
playwrightTest.runWith(envConfig);
116116
slowPlaywrightTest.runWith({ ...envConfig, timeout: config.timeout * 3 });
117117
pageTest.runWith(envConfig, new PageEnv());
118+
tracingTest.runWith({ options: { ...envConfig.options, traceDir: path.join(config.outputDir, 'trace-' + process.env.FOLIO_WORKER_INDEX) }, tag: browserName });
118119
}

tests/tracing.spec.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import path from 'path';
18+
import { expect, tracingTest as test } from './config/browserTest';
19+
import yauzl from 'yauzl';
20+
import removeFolder from 'rimraf';
21+
22+
test.beforeEach(async ({}, testInfo) => {
23+
const folder = path.join(testInfo.config.outputDir, 'trace-' + process.env.FOLIO_WORKER_INDEX);
24+
await new Promise(f => removeFolder(folder, f));
25+
});
26+
27+
test('should collect trace', async ({ context, page, server, browserName }, testInfo) => {
28+
await (context as any)._tracing.start({ name: 'test', screenshots: true, snapshots: true });
29+
await page.goto(server.EMPTY_PAGE);
30+
await page.setContent('<button>Click</button>');
31+
await page.click('"Click"');
32+
await page.close();
33+
await (context as any)._tracing.stop();
34+
await (context as any)._tracing.export(testInfo.outputPath('trace.zip'));
35+
36+
const { events } = await parseTrace(testInfo.outputPath('trace.zip'));
37+
expect(events[0].type).toBe('context-metadata');
38+
expect(events[1].type).toBe('page-created');
39+
expect(events.find(e => e.metadata?.apiName === 'page.goto')).toBeTruthy();
40+
expect(events.find(e => e.metadata?.apiName === 'page.setContent')).toBeTruthy();
41+
expect(events.find(e => e.metadata?.apiName === 'page.click')).toBeTruthy();
42+
expect(events.find(e => e.metadata?.apiName === 'page.close')).toBeTruthy();
43+
44+
expect(events.some(e => e.type === 'frame-snapshot')).toBeTruthy();
45+
expect(events.some(e => e.type === 'resource-snapshot')).toBeTruthy();
46+
if (browserName === 'chromium')
47+
expect(events.some(e => e.type === 'screencast-frame')).toBeTruthy();
48+
});
49+
50+
test('should collect trace', async ({ context, page, server }, testInfo) => {
51+
await (context as any)._tracing.start({ name: 'test' });
52+
await page.goto(server.EMPTY_PAGE);
53+
await page.setContent('<button>Click</button>');
54+
await page.click('"Click"');
55+
await page.close();
56+
await (context as any)._tracing.stop();
57+
await (context as any)._tracing.export(testInfo.outputPath('trace.zip'));
58+
59+
const { events } = await parseTrace(testInfo.outputPath('trace.zip'));
60+
expect(events.some(e => e.type === 'frame-snapshot')).toBeFalsy();
61+
expect(events.some(e => e.type === 'resource-snapshot')).toBeFalsy();
62+
});
63+
64+
test('should collect two traces', async ({ context, page, server }, testInfo) => {
65+
await (context as any)._tracing.start({ name: 'test1', screenshots: true, snapshots: true });
66+
await page.goto(server.EMPTY_PAGE);
67+
await page.setContent('<button>Click</button>');
68+
await page.click('"Click"');
69+
await (context as any)._tracing.stop();
70+
await (context as any)._tracing.export(testInfo.outputPath('trace1.zip'));
71+
72+
await (context as any)._tracing.start({ name: 'test2', screenshots: true, snapshots: true });
73+
await page.dblclick('"Click"');
74+
await page.close();
75+
await (context as any)._tracing.stop();
76+
await (context as any)._tracing.export(testInfo.outputPath('trace2.zip'));
77+
78+
{
79+
const { events } = await parseTrace(testInfo.outputPath('trace1.zip'));
80+
expect(events[0].type).toBe('context-metadata');
81+
expect(events[1].type).toBe('page-created');
82+
expect(events.find(e => e.metadata?.apiName === 'page.goto')).toBeTruthy();
83+
expect(events.find(e => e.metadata?.apiName === 'page.setContent')).toBeTruthy();
84+
expect(events.find(e => e.metadata?.apiName === 'page.click')).toBeTruthy();
85+
expect(events.find(e => e.metadata?.apiName === 'page.dblclick')).toBeFalsy();
86+
expect(events.find(e => e.metadata?.apiName === 'page.close')).toBeFalsy();
87+
}
88+
89+
{
90+
const { events } = await parseTrace(testInfo.outputPath('trace2.zip'));
91+
expect(events[0].type).toBe('context-metadata');
92+
expect(events[1].type).toBe('page-created');
93+
expect(events.find(e => e.metadata?.apiName === 'page.goto')).toBeFalsy();
94+
expect(events.find(e => e.metadata?.apiName === 'page.setContent')).toBeFalsy();
95+
expect(events.find(e => e.metadata?.apiName === 'page.click')).toBeFalsy();
96+
expect(events.find(e => e.metadata?.apiName === 'page.dblclick')).toBeTruthy();
97+
expect(events.find(e => e.metadata?.apiName === 'page.close')).toBeTruthy();
98+
}
99+
});
100+
101+
async function parseTrace(file: string): Promise<{ events: any[], resources: Map<string, Buffer> }> {
102+
const entries = await new Promise<any[]>(f => {
103+
const entries: Promise<any>[] = [];
104+
yauzl.open(file, (err, zipFile) => {
105+
zipFile.on('entry', entry => {
106+
const entryPromise = new Promise(ff => {
107+
zipFile.openReadStream(entry, (err, readStream) => {
108+
const buffers = [];
109+
if (readStream) {
110+
readStream.on('data', d => buffers.push(d));
111+
readStream.on('end', () => ff({ name: entry.fileName, buffer: Buffer.concat(buffers) }));
112+
} else {
113+
ff({ name: entry.fileName });
114+
}
115+
});
116+
});
117+
entries.push(entryPromise);
118+
});
119+
zipFile.on('end', () => f(entries));
120+
});
121+
});
122+
const resources = new Map<string, Buffer>();
123+
for (const { name, buffer } of await Promise.all(entries))
124+
resources.set(name, buffer);
125+
const events = resources.get('trace.trace').toString().split('\n').map(line => line ? JSON.parse(line) : false).filter(Boolean);
126+
return {
127+
events,
128+
resources,
129+
};
130+
}

0 commit comments

Comments
 (0)