diff --git a/packages/integration-tests/suites/public-api/startTransaction/basic_usage/subject.js b/packages/integration-tests/suites/public-api/startTransaction/basic_usage/subject.js new file mode 100644 index 000000000000..c75bd2718326 --- /dev/null +++ b/packages/integration-tests/suites/public-api/startTransaction/basic_usage/subject.js @@ -0,0 +1,30 @@ +const transaction = Sentry.startTransaction({ name: 'test_transaction_1' }); +const span_1 = transaction.startChild({ + op: 'span_1', + data: { + foo: 'bar', + baz: [1, 2, 3], + }, +}); +for (let i = 0; i < 2000; i++); + +// span_1 finishes +span_1.finish(); + +// span_2 doesn't finish +const span_2 = transaction.startChild({ op: 'span_2' }); +for (let i = 0; i < 4000; i++); + +const span_3 = transaction.startChild({ op: 'span_3' }); +for (let i = 0; i < 4000; i++); + +// span_4 is the child of span_3 but doesn't finish. +const span_4 = span_3.startChild({ op: 'span_4', data: { qux: 'quux' } }); + +// span_5 is another child of span_3 but finishes. +const span_5 = span_3.startChild({ op: 'span_5' }).finish(); + +// span_3 also finishes +span_3.finish(); + +transaction.finish(); diff --git a/packages/integration-tests/suites/public-api/startTransaction/basic_usage/test.ts b/packages/integration-tests/suites/public-api/startTransaction/basic_usage/test.ts new file mode 100644 index 000000000000..1dc2eff3febc --- /dev/null +++ b/packages/integration-tests/suites/public-api/startTransaction/basic_usage/test.ts @@ -0,0 +1,34 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getSentryTransactionRequest } from '../../../../utils/helpers'; + +sentryTest('should report a transaction in an envelope', async ({ getLocalTestPath, page }) => { + const url = await getLocalTestPath({ testDir: __dirname }); + const transaction = await getSentryTransactionRequest(page, url); + + expect(transaction.transaction).toBe('test_transaction_1'); + expect(transaction.spans).toBeDefined(); +}); + +sentryTest('should report finished spans as children of the root transaction', async ({ getLocalTestPath, page }) => { + const url = await getLocalTestPath({ testDir: __dirname }); + const transaction = await getSentryTransactionRequest(page, url); + + const rootSpanId = transaction?.contexts?.trace.spanId; + + expect(transaction.spans).toHaveLength(3); + + const span_1 = transaction.spans?.[0]; + expect(span_1?.op).toBe('span_1'); + expect(span_1?.parentSpanId).toEqual(rootSpanId); + expect(span_1?.data).toMatchObject({ foo: 'bar', baz: [1, 2, 3] }); + + const span_3 = transaction.spans?.[1]; + expect(span_3?.op).toBe('span_3'); + expect(span_3?.parentSpanId).toEqual(rootSpanId); + + const span_5 = transaction.spans?.[2]; + expect(span_5?.op).toBe('span_5'); + expect(span_5?.parentSpanId).toEqual(span_3?.spanId); +}); diff --git a/packages/integration-tests/suites/public-api/startTransaction/init.js b/packages/integration-tests/suites/public-api/startTransaction/init.js new file mode 100644 index 000000000000..b326cc489bde --- /dev/null +++ b/packages/integration-tests/suites/public-api/startTransaction/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; +// eslint-disable-next-line no-unused-vars +import * as _ from '@sentry/tracing'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, +}); diff --git a/packages/integration-tests/suites/public-api/startTransaction/template.hbs b/packages/integration-tests/suites/public-api/startTransaction/template.hbs new file mode 100644 index 000000000000..a28a09b7b485 --- /dev/null +++ b/packages/integration-tests/suites/public-api/startTransaction/template.hbs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/integration-tests/utils/helpers.ts b/packages/integration-tests/utils/helpers.ts index 040a3522b9cd..c30621f67135 100644 --- a/packages/integration-tests/utils/helpers.ts +++ b/packages/integration-tests/utils/helpers.ts @@ -1,7 +1,10 @@ -import { Page } from '@playwright/test'; +import { Page, Request } from '@playwright/test'; import { Event } from '@sentry/types'; const storeUrlRegex = /\.sentry\.io\/api\/\d+\/store\//; +const envelopeUrlRegex = /\.sentry\.io\/api\/\d+\/envelope\//; + +type SentryRequestType = 'event' | 'transaction'; /** * Run script at the given path inside the test environment. @@ -15,18 +18,41 @@ async function runScriptInSandbox(page: Page, path: string): Promise { } /** - * Wait and get Sentry's request sending the event at the given URL + * Wait and get Sentry's request sending the event. + * + * @param {Page} page + * @returns {*} {Promise} + */ +async function waitForSentryRequest(page: Page, requestType: SentryRequestType = 'event'): Promise { + return page.waitForRequest(requestType === 'event' ? storeUrlRegex : envelopeUrlRegex); +} + +/** + * Wait and get Sentry's request sending the event at the given URL, or the current page * * @param {Page} page * @param {string} url * @return {*} {Promise} */ -async function getSentryRequest(page: Page, url: string): Promise { - const request = (await Promise.all([page.goto(url), page.waitForRequest(storeUrlRegex)]))[1]; +async function getSentryRequest(page: Page, url?: string): Promise { + const request = (await Promise.all([page.goto(url || '#'), waitForSentryRequest(page)]))[1]; return JSON.parse((request && request.postData()) || ''); } +async function getSentryTransactionRequest(page: Page, url?: string): Promise { + const request = (await Promise.all([page.goto(url || '#'), waitForSentryRequest(page, 'transaction')]))[1]; + + try { + // https://develop.sentry.dev/sdk/envelopes/ + const envelope = request?.postData() || ''; + + // Third row of the envelop is the event payload. + return envelope.split('\n').map(line => JSON.parse(line))[2]; + } catch (err) { + return Promise.reject(err); + } +} /** * Get Sentry events at the given URL, or the current page. * @@ -96,4 +122,12 @@ async function injectScriptAndGetEvents(page: Page, url: string, scriptPath: str return await getSentryEvents(page); } -export { runScriptInSandbox, getMultipleSentryRequests, getSentryRequest, getSentryEvents, injectScriptAndGetEvents }; +export { + runScriptInSandbox, + waitForSentryRequest, + getMultipleSentryRequests, + getSentryRequest, + getSentryTransactionRequest, + getSentryEvents, + injectScriptAndGetEvents, +};