Skip to content

Commit

Permalink
tracing working!
Browse files Browse the repository at this point in the history
source-map added to detect api calls in stacktraces

Refs: #7
  • Loading branch information
ruifigueira committed Nov 12, 2023
1 parent a0b51d0 commit 6644cad
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 18 deletions.
48 changes: 34 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"process": "^0.11.10",
"readable-stream": "^4.4.2",
"setimmediate": "^1.0.5",
"source-map": "^0.7.4",
"stream-http": "^3.2.0",
"string_decoder": "^1.3.0",
"test-utils-bundle": "file:./playwright/packages/playwright/bundles/utils",
Expand Down
6 changes: 4 additions & 2 deletions playwright/packages/playwright-core/src/utils/stackTrace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function rewriteErrorMessage<E extends Error>(e: E, newMessage: string):
return e;
}

const CORE_DIR = path.resolve(__dirname, '..', '..');
const CORE_DIR = '/playwright/packages/playwright-core';
const COVERAGE_PATH = path.join(CORE_DIR, '..', '..', 'tests', 'config', 'coverage.js');

const internalStackPrefixes = [
Expand All @@ -48,7 +48,9 @@ export function captureRawStack(): RawStack {
}

export function captureLibraryStackTrace(rawStack?: RawStack): { frames: StackFrame[], apiName: string } {
const stack = rawStack || captureRawStack();
rawStack = rawStack || captureRawStack();
const stacktraceSourcemap = (self as any)?._stacktraceSourcemap;
const stack: string[] = stacktraceSourcemap?.processSourceMaps(rawStack) ?? rawStack;

const isTesting = isUnderTest();
type ParsedFrame = {
Expand Down
3 changes: 3 additions & 0 deletions src/shims/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ import './process';
import './setImmediate';
import './buffer';
import { fs } from 'memfs';
import { stacktraceSourcemap } from '../utils/stacktraceSourcemap';

fs.mkdirSync('/tmp');
fs.mkdirSync('/crx');

self.global = self;
self.__dirname = '/crx';
// @ts-ignore
self._stacktraceSourcemap = stacktraceSourcemap;
Binary file added src/shims/source-map/mappings.wasm
Binary file not shown.
15 changes: 15 additions & 0 deletions src/shims/source-map/read-wasm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @ts-ignore
import mappingsUrl from './mappings.wasm?url';

export default async function readWasm() {
if (mappingsUrl.startsWith('data:')) {
const [,base64] = mappingsUrl.split(',');
return Buffer.from(base64, 'base64').buffer;
}
const response = await fetch(mappingsUrl);
return await response.arrayBuffer();
};

export const initialize = () => {
// nothing to do
};
74 changes: 74 additions & 0 deletions src/utils/stacktraceSourcemap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// @ts-ignore
import { SourceMapConsumer } from 'source-map/lib/source-map-consumer';

const regex = /^ +at.+\((.*):([0-9]+):([0-9]+)/;

class StacktraceSourcemap {

_mapForUri = new Map<string, SourceMapConsumer | undefined>();

async register(uri: string) {
if (this._mapForUri.has(uri)) return;
this._mapForUri.set(uri, undefined);
await new Promise<void>(async resolve => {
try {
const result = await fetch(`${uri}.map`);
const sourceMap = await result.json();
const sourceMapConsumer = result.ok ? await new SourceMapConsumer(sourceMap) : undefined;
this._mapForUri.set(uri, sourceMapConsumer);
} catch (e) {
console.error(e);
} finally {
resolve();
}
});
}

processSourceMaps([firstLine, ...stack]: string[]): string[] {
const result = [firstLine];

for (const line of stack) {
const [_, uri, lineNumberStr, columnStr] = line.match(regex) ?? [];
if (uri) {
const lineNumber = parseInt(lineNumberStr, 10);
const column = parseInt(columnStr, 10);
const map = this._mapForUri.get(uri);

if (map) {
// we think we have a map for that uri. call source-map library
var origPos = map.originalPositionFor({ line: lineNumber, column });
if (origPos.source && origPos.line && origPos.column) {
result.push(this._formatOriginalPosition(
origPos.source,
origPos.line,
origPos.column,
origPos.name || this._origName(line)
));
}
} else {
// we can't find a map for that url, but we parsed the row.
// reformat unchanged line for consistency with the sourcemapped
// lines.
result.push(this._formatOriginalPosition(uri, lineNumber, column, this._origName(line)));
}
} else {
// we weren't able to parse the row, push back what we were given
result.push(line);
}
}

return result;
}

_origName(origLine: string) {
const [, name] = / +at +([^ ]*).*/.exec(origLine) ?? [];
return name;
}

_formatOriginalPosition(source: string, line: number, column: number, name?: string) {
// mimic chrome's format
return ` at ${name ?? "(unknown)"} (${source}:${line}:${column})`;
};
}

export const stacktraceSourcemap = new StacktraceSourcemap();
22 changes: 22 additions & 0 deletions tests/crx/api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,25 @@ test('should not block on pages with service workers', async ({ runCrxTest }) =>
await expect(page.evaluate(() => window['registrationPromise'])).resolves.toBeTruthy();
});
});

test('should trace', async ({ runCrxTest, _extensionServiceWorkerDevtools }) => {
await runCrxTest(async ({ crxApp, page, context }) => {
await context.tracing.start({ snapshots: true, screenshots: true, sources: true });
await page.goto('https://demo.playwright.dev/todomvc/#/');
await page.getByPlaceholder('What needs to be done?').fill('Playwright CRX rocks!');
await page.getByPlaceholder('What needs to be done?').press('Enter');
await page.getByLabel('Toggle Todo').click();
await page.getByRole('button', { name: 'Clear completed' }).click();
await context.tracing.stop({ path: '/crx/trace.zip' });

const tracePage = await crxApp.newPage();
await tracePage.goto('https://trace.playwright.dev');

const [chooser] = await Promise.all([
tracePage.waitForEvent('filechooser'),
tracePage.getByRole('button', { name: 'Select file(s)' }).click(),
]);
await chooser.setFiles('/crx/trace.zip');
debugger;
});
});
7 changes: 5 additions & 2 deletions tests/test-extension/public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
"version": "0.1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"service_worker": "sw.js",
"type": "module"
},
"permissions": ["debugger", "tabs"]
"permissions": ["debugger", "tabs"],
"content_security_policy": {
"extension_pages": "default-src 'wasm-unsafe-eval'"
}
}
3 changes: 3 additions & 0 deletions tests/test-extension/public/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import './background.js';

self._stacktraceSourcemap.register(chrome.runtime.getURL('background.js')).catch(() => {});
3 changes: 3 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ export default defineConfig({
'node:events': path.resolve(__dirname, './node_modules/events'),
'node:stream': path.resolve(__dirname, './node_modules/readable-stream'),
'node:string_decoder': path.resolve(__dirname, './node_modules/string_decoder'),

// to work with service workers
'../lib/read-wasm': path.resolve(__dirname, './src/shims/source-map/read-wasm'),
},
},
define: {
Expand Down

0 comments on commit 6644cad

Please sign in to comment.