diff --git a/src/debug/sourceMap.ts b/src/debug/sourceMap.ts index b2762f66c4..ac6cebcb45 100644 --- a/src/debug/sourceMap.ts +++ b/src/debug/sourceMap.ts @@ -16,10 +16,7 @@ import * as fs from 'fs'; import * as util from 'util'; -import * as path from 'path'; - -// NOTE: update this to point to playwright/lib when moving this file. -const PLAYWRIGHT_LIB_PATH = path.normalize(path.join(__dirname, '..')); +import { getCallerFilePath } from './stackTrace'; type Position = { line: number; @@ -118,28 +115,3 @@ function advancePosition(position: Position, delta: Position) { column: delta.column + (delta.line ? 0 : position.column), }; } - -function getCallerFilePath(): string | null { - const error = new Error(); - const stackFrames = (error.stack || '').split('\n').slice(1); - // Find first stackframe that doesn't point to PLAYWRIGHT_LIB_PATH. - for (let frame of stackFrames) { - frame = frame.trim(); - if (!frame.startsWith('at ')) - return null; - if (frame.endsWith(')')) { - const from = frame.indexOf('('); - frame = frame.substring(from + 1, frame.length - 1); - } else { - frame = frame.substring('at '.length); - } - const match = frame.match(/^(?:async )?(.*):(\d+):(\d+)$/); - if (!match) - return null; - const filePath = match[1]; - if (filePath.startsWith(PLAYWRIGHT_LIB_PATH)) - continue; - return filePath; - } - return null; -} diff --git a/src/debug/stackTrace.ts b/src/debug/stackTrace.ts new file mode 100644 index 0000000000..0d5cb819b5 --- /dev/null +++ b/src/debug/stackTrace.ts @@ -0,0 +1,81 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as path from 'path'; + +// NOTE: update this to point to playwright/lib when moving this file. +const PLAYWRIGHT_LIB_PATH = path.normalize(path.join(__dirname, '..')); + +type ParsedStackFrame = { filePath: string, functionName: string }; + +function parseStackFrame(frame: string): ParsedStackFrame | null { + frame = frame.trim(); + if (!frame.startsWith('at ')) + return null; + frame = frame.substring('at '.length); + if (frame.startsWith('async ')) + frame = frame.substring('async '.length); + let location: string; + let functionName: string; + if (frame.endsWith(')')) { + const from = frame.indexOf('('); + location = frame.substring(from + 1, frame.length - 1); + functionName = frame.substring(0, from).trim(); + } else { + location = frame; + functionName = ''; + } + const match = location.match(/^(?:async )?([^(]*):(\d+):(\d+)$/); + if (!match) + return null; + const filePath = match[1]; + return { filePath, functionName }; +} + +export function getCallerFilePath(ignorePrefix = PLAYWRIGHT_LIB_PATH): string | null { + const error = new Error(); + const stackFrames = (error.stack || '').split('\n').slice(1); + // Find first stackframe that doesn't point to ignorePrefix. + for (const frame of stackFrames) { + const parsed = parseStackFrame(frame); + if (!parsed) + return null; + if (parsed.filePath.startsWith(ignorePrefix) || parsed.filePath === __filename) + continue; + return parsed.filePath; + } + return null; +} + +export function getCurrentApiCall(prefix = PLAYWRIGHT_LIB_PATH): string { + const error = new Error(); + const stackFrames = (error.stack || '').split('\n').slice(1); + // Find last stackframe that points to prefix - that should be the api call. + let apiName: string = ''; + for (const frame of stackFrames) { + const parsed = parseStackFrame(frame); + if (!parsed || (!parsed.filePath.startsWith(prefix) && parsed.filePath !== __filename)) + break; + apiName = parsed.functionName; + } + const parts = apiName.split('.'); + if (parts.length && parts[0].length) { + parts[0] = parts[0][0].toLowerCase() + parts[0].substring(1); + if (parts[0] === 'webKit') + parts[0] = 'webkit'; + } + return parts.join('.'); +} diff --git a/test/fixtures.spec.js b/test/fixtures.spec.js index 196857f217..a356f7f237 100644 --- a/test/fixtures.spec.js +++ b/test/fixtures.spec.js @@ -177,3 +177,20 @@ describe('Fixtures', function() { }); }); }); + +describe('StackTrace', () => { + it('caller file path', async state => { + const stackTrace = require(path.join(state.playwrightPath, 'lib', 'debug', 'stackTrace')); + const callme = require('./fixtures/callback'); + const filePath = callme(() => { + return stackTrace.getCallerFilePath(path.join(__dirname, 'fixtures') + path.sep); + }); + expect(filePath).toBe(__filename); + }); + it('api call', async state => { + const stackTrace = require(path.join(state.playwrightPath, 'lib', 'debug', 'stackTrace')); + const callme = require('./fixtures/callback'); + const apiCall = callme(stackTrace.getCurrentApiCall.bind(stackTrace, path.join(__dirname, 'fixtures') + path.sep)); + expect(apiCall).toBe('callme'); + }); +}); diff --git a/test/fixtures/callback.js b/test/fixtures/callback.js new file mode 100644 index 0000000000..1393adeacb --- /dev/null +++ b/test/fixtures/callback.js @@ -0,0 +1,5 @@ +function callme(cb) { + return cb(); +} + +module.exports = callme;