From cbad255587ddc867e5a1afefdac4dd03f0f1cbee Mon Sep 17 00:00:00 2001 From: vigneshshanmugam Date: Mon, 21 Mar 2022 08:55:45 -0700 Subject: [PATCH 1/2] feat: location info for journey and step --- __tests__/helper.test.ts | 8 ++++++++ package-lock.json | 13 ++++++++----- package.json | 4 ++-- src/cli.ts | 2 +- src/common_types.ts | 19 +++++++++++++------ src/core/index.ts | 38 +++++++++++++++++++++---------------- src/dsl/journey.ts | 14 ++++++++++---- src/dsl/step.ts | 11 +++++++++-- src/helpers.ts | 41 +++++++++++++++++++++++++++++++++++++++- src/index.ts | 8 -------- tsconfig.json | 2 +- 11 files changed, 114 insertions(+), 46 deletions(-) diff --git a/__tests__/helper.test.ts b/__tests__/helper.test.ts index 83ccbe0a..ea1fb113 100644 --- a/__tests__/helper.test.ts +++ b/__tests__/helper.test.ts @@ -33,6 +33,7 @@ import { rewriteErrorStack, findPWLogsIndexes, microSecsToSeconds, + wrapFnWithLocation, } from '../src/helpers'; it('indent message with seperator', () => { @@ -133,3 +134,10 @@ it('does not rewrite non playwright errors', () => { const newNormalStack = rewriteErrorStack(normalStack, indexes); expect(normalStack).toStrictEqual(newNormalStack); }); + +it('location info on execution', () => { + const checkLoc = wrapFnWithLocation(location => { + return location; + }); + expect(checkLoc().file).toBe(__filename); +}); diff --git a/package-lock.json b/package-lock.json index f52c66f0..360cd6b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1800,7 +1800,8 @@ "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true }, "callsites": { "version": "3.1.0", @@ -5873,12 +5874,14 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -6151,9 +6154,9 @@ } }, "ts-node": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.5.0.tgz", - "integrity": "sha512-6kEJKwVxAJ35W4akuiysfKwKmjkbYxwQMTBaAxo9KKAx/Yd26mPUyhGz3ji+EsJoAgrLqVsYHNuuYwQe22lbtw==", + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", "requires": { "@cspotcode/source-map-support": "0.7.0", "@tsconfig/node10": "^1.0.7", diff --git a/package.json b/package.json index ab24fbdd..a6f85d28 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "author": "", "license": "MIT", "dependencies": { + "@cspotcode/source-map-support": "^0.7.0", "commander": "^9.0.0", "deepmerge": "^4.2.2", "expect": "^27.0.2", @@ -48,8 +49,7 @@ "sharp": "^0.30.1", "snakecase-keys": "^3.2.1", "sonic-boom": "^2.6.0", - "source-map-support": "^0.5.21", - "ts-node": "^10.5.0", + "ts-node": "^10.7.0", "typescript": "^4.5.5" }, "devDependencies": { diff --git a/src/cli.ts b/src/cli.ts index 078dddaa..d4ba124d 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -141,7 +141,7 @@ async function prepareSuites(inputs: string[]) { compilerOptions: { esModuleInterop: true, allowJs: true, - target: 'es2018', + target: 'es2020', }, }); diff --git a/src/common_types.ts b/src/common_types.ts index 227139e8..956f73af 100644 --- a/src/common_types.ts +++ b/src/common_types.ts @@ -36,13 +36,13 @@ import { Step } from './dsl'; import { reporters } from './reporters'; export type VoidCallback = () => void; -export type Params = Record; -export type NetworkConditions = { - offline: boolean; - downloadThroughput: number; - uploadThroughput: number; - latency: number; +export type Location = { + file: string; + line: number; + column: number; }; + +export type Params = Record; export type HooksArgs = { env: string; params: Params; @@ -51,6 +51,13 @@ export type HooksCallback = (args: HooksArgs) => void; export type StatusValue = 'succeeded' | 'failed' | 'skipped'; export type Reporters = keyof typeof reporters; +export type NetworkConditions = { + offline: boolean; + downloadThroughput: number; + uploadThroughput: number; + latency: number; +}; + export type Driver = { browser: ChromiumBrowser; context: ChromiumBrowserContext; diff --git a/src/core/index.ts b/src/core/index.ts index 2a5be466..ce89b4f9 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -25,7 +25,8 @@ import { Journey, JourneyCallback, JourneyOptions } from '../dsl'; import Runner from './runner'; -import { VoidCallback, HooksCallback } from '../common_types'; +import { VoidCallback, HooksCallback, Location } from '../common_types'; +import { wrapFnWithLocation } from '../helpers'; import { log } from './logger'; /** @@ -39,23 +40,28 @@ if (!global[SYNTHETICS_RUNNER]) { export const runner: Runner = global[SYNTHETICS_RUNNER]; -export const journey = ( - options: JourneyOptions | string, - callback: JourneyCallback -) => { - log(`register journey: ${JSON.stringify(options)}`); - if (typeof options === 'string') { - options = { name: options, id: options }; +export const journey = wrapFnWithLocation( + ( + location: Location, + options: JourneyOptions | string, + callback: JourneyCallback + ) => { + log(`register journey: ${JSON.stringify(options)}`); + if (typeof options === 'string') { + options = { name: options, id: options }; + } + const j = new Journey(options, callback, location); + runner.addJourney(j); + return j; } - const j = new Journey(options, callback); - runner.addJourney(j); - return j; -}; +); -export const step = (name: string, callback: VoidCallback) => { - log(`register step: ${name}`); - return runner.currentJourney?.addStep(name, callback); -}; +export const step = wrapFnWithLocation( + (location: Location, name: string, callback: VoidCallback) => { + log(`register step: ${name}`); + return runner.currentJourney?.addStep(name, callback, location); + } +); export const beforeAll = (callback: HooksCallback) => { runner.addHook('beforeAll', callback); diff --git a/src/dsl/journey.ts b/src/dsl/journey.ts index 2bdc55ab..22aa1443 100644 --- a/src/dsl/journey.ts +++ b/src/dsl/journey.ts @@ -26,7 +26,7 @@ import { Browser, Page, BrowserContext, CDPSession } from 'playwright-chromium'; import micromatch, { isMatch } from 'micromatch'; import { Step } from './step'; -import { VoidCallback, HooksCallback, Params } from '../common_types'; +import { VoidCallback, HooksCallback, Params, Location } from '../common_types'; export type JourneyOptions = { name: string; @@ -49,18 +49,24 @@ export class Journey { id?: string; tags?: string[]; callback: JourneyCallback; + location?: Location; steps: Step[] = []; hooks: Hooks = { before: [], after: [] }; - constructor(options: JourneyOptions, callback: JourneyCallback) { + constructor( + options: JourneyOptions, + callback: JourneyCallback, + location?: Location + ) { this.name = options.name; this.id = options.id || options.name; this.tags = options.tags; this.callback = callback; + this.location = location; } - addStep(name: string, callback: VoidCallback) { - const step = new Step(name, this.steps.length + 1, callback); + addStep(name: string, callback: VoidCallback, location?: Location) { + const step = new Step(name, this.steps.length + 1, callback, location); this.steps.push(step); return step; } diff --git a/src/dsl/step.ts b/src/dsl/step.ts index 8ed81a62..44148ee6 100644 --- a/src/dsl/step.ts +++ b/src/dsl/step.ts @@ -23,16 +23,23 @@ * */ -import { VoidCallback } from '../common_types'; +import { Location, VoidCallback } from '../common_types'; export class Step { name: string; index: number; callback: VoidCallback; + location?: Location; - constructor(name: string, index: number, callback: VoidCallback) { + constructor( + name: string, + index: number, + callback: VoidCallback, + location: Location + ) { this.name = name; this.index = index; this.callback = callback; + this.location = location; } } diff --git a/src/helpers.ts b/src/helpers.ts index e74e71f7..76d2a57e 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -29,7 +29,13 @@ import { resolve, join, dirname } from 'path'; import fs from 'fs'; import { promisify } from 'util'; import { performance } from 'perf_hooks'; -import { HooksArgs, HooksCallback, NetworkConditions } from './common_types'; +import sourceMapSupport from '@cspotcode/source-map-support'; +import { + HooksArgs, + HooksCallback, + NetworkConditions, + Location, +} from './common_types'; const lstatAsync = promisify(fs.lstat); const readdirAsync = promisify(fs.readdir); @@ -315,3 +321,36 @@ export function parseNetworkConditions(args: string): NetworkConditions { return networkConditions; } + +// default stack trace limit +const dstackTraceLimit = 10; + +// Uses the V8 Stacktrace API to get the function location +// information - https://v8.dev/docs/stack-trace-api#customizing-stack-traces +export function wrapFnWithLocation( + func: (location: Location, ...args: A) => R +): (...args: A) => R { + return (...args) => { + const _prepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = (_, stackFrames) => { + // Deafult CallSite would not map to the original transpiled source + // from ts-node properly, So we wrap it with the library that knows + // how to retrive those source-map for the transpiled code + const frame: NodeJS.CallSite = sourceMapSupport.wrapCallSite( + stackFrames[1] + ); + return { + file: frame.getFileName(), + line: frame.getLineNumber(), + column: frame.getColumnNumber(), + }; + }; + Error.stackTraceLimit = 2; + const obj: { stack: Location } = {} as any; + Error.captureStackTrace(obj); + const location = obj.stack; + Error.stackTraceLimit = dstackTraceLimit; + Error.prepareStackTrace = _prepareStackTrace; + return func(location, ...args); + }; +} diff --git a/src/index.ts b/src/index.ts index ae0c8288..6af7a217 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,16 +25,8 @@ import { runner } from './core'; import { RunOptions } from './core/runner'; -import sourceMapSupport from 'source-map-support'; export async function run(options: RunOptions) { - /** - * Install source map support - */ - sourceMapSupport.install({ - environment: 'node', - }); - try { return await runner.run(options); } catch (e) { diff --git a/tsconfig.json b/tsconfig.json index ce0d1ecb..1605b9ae 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "noImplicitOverride": true, "sourceMap": true, "outDir": "dist", - "target": "es2018", + "target": "es2020", "module": "commonjs" }, "include": ["src/**/*"] From da85263ee154c8662555f2e8c49dbf2df31fef00 Mon Sep 17 00:00:00 2001 From: vigneshshanmugam Date: Mon, 21 Mar 2022 14:02:10 -0700 Subject: [PATCH 2/2] add comment on line and column check --- __tests__/helper.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/__tests__/helper.test.ts b/__tests__/helper.test.ts index ea1fb113..b77fd745 100644 --- a/__tests__/helper.test.ts +++ b/__tests__/helper.test.ts @@ -139,5 +139,7 @@ it('location info on execution', () => { const checkLoc = wrapFnWithLocation(location => { return location; }); + // line no and column no will not match as we are using + // ts-jest preset to transpile code. expect(checkLoc().file).toBe(__filename); });