From 54a0b710ca690c9b0dffefd92d3e1cb5f0f0854b Mon Sep 17 00:00:00 2001 From: Chunwai Li Date: Tue, 27 Jun 2023 15:21:13 -0500 Subject: [PATCH 01/14] Tracking disabled --- tests/specs/stn/with-session-replay.e2e.js | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/specs/stn/with-session-replay.e2e.js diff --git a/tests/specs/stn/with-session-replay.e2e.js b/tests/specs/stn/with-session-replay.e2e.js new file mode 100644 index 000000000..12a83f9de --- /dev/null +++ b/tests/specs/stn/with-session-replay.e2e.js @@ -0,0 +1,51 @@ +import { testRumRequest } from '../../../tools/testing-server/utils/expect-tests' + +describe('Trace behavior when session tracking is disabled', () => { + const rumFlags = { + err: 1, + ins: 1, + cap: 1, + spa: 1, + loaded: 1 + } + let stPayloadReceived, getUrlString + beforeEach(() => { + stPayloadReceived = undefined + browser.testHandle.expectResources().then(() => stPayloadReceived = true) + getUrlString = browser.testHandle.assetURL('stn/instrumented.html', { + init: { + privacy: { cookies_enabled: false } + } + }) + }) + + it('does not run if stn flag is 0', async () => { + await browser.testHandle.scheduleReply('bamServer', { + test: testRumRequest, + body: JSON.stringify({ stn: 0, ...rumFlags }) + }) + + await getUrlString.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(() => { + expect(stPayloadReceived).toBeUndefined() // trace payload should've been received by now after page has loaded + }) + }) + + it('does run (standalone behavior) if stn flag is 1', async () => { + await browser.testHandle.scheduleReply('bamServer', { + test: testRumRequest, + body: JSON.stringify({ stn: 1, ...rumFlags }) + }) + + await getUrlString.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(async () => { + const getTraceMode = browser.execute(function () { // expect Trace to be running by itself + return Object.values(newrelic.initializedAgents)[0].features.session_trace.featAggregate.isStandalone + }) + expect(stPayloadReceived).toBeTruthy() + await expect(getTraceMode).resolves.toBeTruthy() + }) + }) +}) + +describe('Trace behavior when session tracking is enabled', () => { + +}) From aede465c81c05c6f282d0dbce9c04fd0b4f312eb Mon Sep 17 00:00:00 2001 From: Chunwai Li Date: Tue, 27 Jun 2023 15:21:13 -0500 Subject: [PATCH 02/14] tracking enabled with replay off --- tests/specs/stn/with-session-replay.e2e.js | 77 +++++++++++----------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/tests/specs/stn/with-session-replay.e2e.js b/tests/specs/stn/with-session-replay.e2e.js index 12a83f9de..b42dbd65f 100644 --- a/tests/specs/stn/with-session-replay.e2e.js +++ b/tests/specs/stn/with-session-replay.e2e.js @@ -1,51 +1,54 @@ import { testRumRequest } from '../../../tools/testing-server/utils/expect-tests' -describe('Trace behavior when session tracking is disabled', () => { - const rumFlags = { - err: 1, - ins: 1, - cap: 1, - spa: 1, - loaded: 1 - } - let stPayloadReceived, getUrlString - beforeEach(() => { - stPayloadReceived = undefined - browser.testHandle.expectResources().then(() => stPayloadReceived = true) - getUrlString = browser.testHandle.assetURL('stn/instrumented.html', { - init: { - privacy: { cookies_enabled: false } - } +[ + { case: 'session tracking is disabled', trackingOn: false }, + { case: 'session tracking enabled but replay is off', trackingOn: true } +].forEach(runArgs => { + describe(`Trace behavior when ${runArgs.case}`, () => { + let stPayloadReceived, getUrlString + beforeEach(() => { + stPayloadReceived = undefined + browser.testHandle.expectResources().then(() => stPayloadReceived = true) + getUrlString = browser.testHandle.assetURL('stn/instrumented.html', { + init: { + privacy: { cookies_enabled: runArgs.trackingOn } + } + }) }) - }) - it('does not run if stn flag is 0', async () => { - await browser.testHandle.scheduleReply('bamServer', { - test: testRumRequest, - body: JSON.stringify({ stn: 0, ...rumFlags }) - }) + it('does not run if stn flag is 0', async () => { + await browser.testHandle.scheduleReply('bamServer', { + test: testRumRequest, + body: JSON.stringify({ + stn: 0, + err: 1, + ins: 1, + cap: 1, + spa: 1, + sr: 0, + loaded: 1 + }) + }) - await getUrlString.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(() => { - expect(stPayloadReceived).toBeUndefined() // trace payload should've been received by now after page has loaded + await getUrlString.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(() => { + expect(stPayloadReceived).toBeUndefined() // trace payload should've been received by now after page has loaded + }) }) - }) - it('does run (standalone behavior) if stn flag is 1', async () => { - await browser.testHandle.scheduleReply('bamServer', { - test: testRumRequest, - body: JSON.stringify({ stn: 1, ...rumFlags }) - }) + it('does run (standalone behavior) if stn flag is 1', async () => { + // The default rum response will include stn = 1 and sr = 0. - await getUrlString.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(async () => { - const getTraceMode = browser.execute(function () { // expect Trace to be running by itself - return Object.values(newrelic.initializedAgents)[0].features.session_trace.featAggregate.isStandalone + await getUrlString.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(async () => { + const getTraceMode = browser.execute(function () { // expect Trace to be running by itself + return Object.values(newrelic.initializedAgents)[0].features.session_trace.featAggregate.isStandalone + }) + expect(stPayloadReceived).toBeTruthy() + await expect(getTraceMode).resolves.toBeTruthy() }) - expect(stPayloadReceived).toBeTruthy() - await expect(getTraceMode).resolves.toBeTruthy() }) }) }) -describe('Trace behavior when session tracking is enabled', () => { +// describe('Trace behavior when session tracking is enabled', () => { -}) +// }) From 9de5f860917c5c21b561e765f50a2649aa9c41c4 Mon Sep 17 00:00:00 2001 From: Chunwai Li Date: Tue, 27 Jun 2023 18:49:34 -0500 Subject: [PATCH 03/14] replay entitlement on but feature is off --- src/features/session_trace/aggregate/index.js | 5 +- src/features/utils/instrument-base.js | 3 +- tests/specs/session-replay/helpers.js | 5 + .../specs/session-replay/session-state.e2e.js | 9 +- tests/specs/stn/with-session-replay.e2e.js | 94 +++++++++++++++++-- tools/testing-server/routes/bam-apis.js | 3 +- 6 files changed, 98 insertions(+), 21 deletions(-) diff --git a/src/features/session_trace/aggregate/index.js b/src/features/session_trace/aggregate/index.js index 90d2cdb89..88f1f1ee3 100644 --- a/src/features/session_trace/aggregate/index.js +++ b/src/features/session_trace/aggregate/index.js @@ -122,9 +122,8 @@ export class Aggregate extends AggregateBase { this.ee.on(SESSION_EVENTS.PAUSE, () => mostRecentModeKnown = sessionEntity.state.sessionTraceMode) if (!sessionEntity.isNew) { // inherit the same mode as existing session's Trace - const existingTraceMode = mostRecentModeKnown = sessionEntity.state.sessionTraceMode - if (existingTraceMode === MODE.OFF) this.isStandalone = true - controlTraceOp(existingTraceMode) + if (sessionEntity.state.sessionReplay === MODE.OFF) this.isStandalone = true + controlTraceOp(mostRecentModeKnown = sessionEntity.state.sessionTraceMode) } else { // for new sessions, see the truth table associated with NEWRELIC-8662 wrt the new Trace behavior under session management const replayMode = await getSessionReplayMode(agentIdentifier) if (replayMode === MODE.OFF) this.isStandalone = true // without SR, Traces are still subject to old harvest limits diff --git a/src/features/utils/instrument-base.js b/src/features/utils/instrument-base.js index 0d7d06c87..8b36df2d2 100644 --- a/src/features/utils/instrument-base.js +++ b/src/features/utils/instrument-base.js @@ -55,7 +55,7 @@ export class InstrumentBase extends FeatureBase { importAggregator (argsObjFromInstrument = {}) { if (this.featAggregate || !this.auto) return const enableSessionTracking = isBrowserScope && getConfigurationValue(this.agentIdentifier, 'privacy.cookies_enabled') === true - let loadedSuccessfully, loadFailed + let loadedSuccessfully this.onAggregateImported = new Promise(resolve => { loadedSuccessfully = resolve }) @@ -78,6 +78,7 @@ export class InstrumentBase extends FeatureBase { try { if (!this.shouldImportAgg(this.featureName, session)) { drain(this.agentIdentifier, this.featureName) + loadedSuccessfully(false) // aggregate module isn't loaded at all return } const { lazyFeatureLoader } = await import(/* webpackChunkName: "lazy-feature-loader" */ './lazy-feature-loader') diff --git a/tests/specs/session-replay/helpers.js b/tests/specs/session-replay/helpers.js index fb0ea9841..b37127844 100644 --- a/tests/specs/session-replay/helpers.js +++ b/tests/specs/session-replay/helpers.js @@ -1,5 +1,10 @@ import { deepmerge } from 'deepmerge-ts' +export const MODE = { + OFF: 0, + FULL: 1, + ERROR: 2 +} export const RRWEB_EVENT_TYPES = { DomContentLoaded: 0, Load: 1, diff --git a/tests/specs/session-replay/session-state.e2e.js b/tests/specs/session-replay/session-state.e2e.js index 18c77eadc..9c0f80761 100644 --- a/tests/specs/session-replay/session-state.e2e.js +++ b/tests/specs/session-replay/session-state.e2e.js @@ -1,12 +1,5 @@ import { supportsMultipleTabs, notIE } from '../../../tools/browser-matcher/common-matchers.mjs' -import { RRWEB_EVENT_TYPES, config, getSR } from './helpers.js' - -/** The "mode" with which the session replay is recording */ -const MODE = { - OFF: 0, - FULL: 1, - ERROR: 2 -} +import { RRWEB_EVENT_TYPES, config, getSR, MODE } from './helpers.js' describe.withBrowsersMatching(notIE)('session manager state behavior', () => { beforeEach(async () => { diff --git a/tests/specs/stn/with-session-replay.e2e.js b/tests/specs/stn/with-session-replay.e2e.js index b42dbd65f..3404014dc 100644 --- a/tests/specs/stn/with-session-replay.e2e.js +++ b/tests/specs/stn/with-session-replay.e2e.js @@ -1,17 +1,18 @@ import { testRumRequest } from '../../../tools/testing-server/utils/expect-tests' +import { config, MODE } from '../session-replay/helpers' [ - { case: 'session tracking is disabled', trackingOn: false }, - { case: 'session tracking enabled but replay is off', trackingOn: true } -].forEach(runArgs => { - describe(`Trace behavior when ${runArgs.case}`, () => { + ['session tracking is disabled', false], + ['session tracking enabled but replay entitlement is 0', true] +].forEach(([run, trackingOn]) => { + describe(`Trace behavior when ${run}`, () => { let stPayloadReceived, getUrlString beforeEach(() => { stPayloadReceived = undefined browser.testHandle.expectResources().then(() => stPayloadReceived = true) getUrlString = browser.testHandle.assetURL('stn/instrumented.html', { init: { - privacy: { cookies_enabled: runArgs.trackingOn } + privacy: { cookies_enabled: trackingOn } } }) }) @@ -23,7 +24,6 @@ import { testRumRequest } from '../../../tools/testing-server/utils/expect-tests stn: 0, err: 1, ins: 1, - cap: 1, spa: 1, sr: 0, loaded: 1 @@ -49,6 +49,84 @@ import { testRumRequest } from '../../../tools/testing-server/utils/expect-tests }) }) -// describe('Trace behavior when session tracking is enabled', () => { +describe('Trace behavior when session tracking enabled and replay entitlement is 1', () => { + const rumFlags = { + stn: 1, + err: 1, + ins: 1, + spa: 1, + sr: 1, + loaded: 1 + } + // let getUrlString + let initSTReceived + beforeEach(async () => { + await browser.destroyAgentSession() + initSTReceived = undefined + browser.testHandle.expectResources().then(resPayload => initSTReceived = resPayload) + // getUrlString = browser.testHandle.assetURL('stn/instrumented.html', config({ + // session_trace: { harvestTimeSeconds: 5 } + // })) + }) + afterEach(async () => { + await browser.testHandle.clearScheduledReplies('bamServer') + }) + + it('still runs when replay feature is missing', async () => { + await browser.testHandle.scheduleReply('bamServer', { + test: testRumRequest, + permanent: true, + body: JSON.stringify(rumFlags) + }) + + const urlWithoutReplay = await browser.testHandle.assetURL('stn/instrumented.html', { init: { privacy: { cookies_enabled: true } } }) + const getTraceInfo = () => browser.execute(function () { + return [ + Object.values(newrelic.initializedAgents)[0].features.session_trace.featAggregate.isStandalone, + Object.values(newrelic.initializedAgents)[0].runtime.session.state.sessionTraceMode + ] + }) + + await browser.url(urlWithoutReplay).then(() => browser.waitForAgentLoad()).then(async () => { + expect(initSTReceived).toBeTruthy() // that is, trace should still fully run when the replay feature isn't around + await expect(getTraceInfo()).resolves.toEqual([true, MODE.FULL]) + + return Promise.all([browser.testHandle.expectResources(3000), browser.refresh().then(() => browser.waitForAgentLoad())]) + }).then(async ([secondInitST]) => { + // On subsequent page load or refresh, trace should maintain the set mode, standalone, and same sessionid but have a new ptid corresponding to new page visit. + expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) + expect(secondInitST.request.query.ptid).not.toEqual(initSTReceived.request.query.ptid) + await expect(getTraceInfo()).resolves.toEqual([true, MODE.FULL]) // note it's expected & assumed that the replay mode is OFF + }) + }) + + it('still runs when replay feature is present but disabled', async () => { + await browser.testHandle.scheduleReply('bamServer', { + test: testRumRequest, + permanent: true, + body: JSON.stringify(rumFlags) + }) + + const urlWithoutReplay = await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: { sampleRate: 0, errorSampleRate: 0 } })) + const getTraceInfo = () => browser.execute(function () { + const agent = Object.values(newrelic.initializedAgents)[0] + return [ + agent.features.session_trace.featAggregate.isStandalone, + agent.runtime.session.state.sessionTraceMode, + agent.features.session_replay.featAggregate?.initialized // expect replay to be fully imported and intialized, but in OFF mode per config above, via isStandalone = true + ] + }) -// }) + await browser.url(urlWithoutReplay).then(() => browser.waitForAgentLoad()).then(async () => { + expect(initSTReceived).toBeTruthy() // that is, trace should still fully run when the replay feature isn't around + await expect(getTraceInfo()).resolves.toEqual([true, MODE.FULL, true]) + + return Promise.all([browser.testHandle.expectResources(3000), browser.refresh().then(() => browser.waitForAgentLoad())]) + }).then(async ([secondInitST]) => { + // On subsequent page load or refresh, trace should maintain the set mode, standalone, and same sessionid but have a new ptid corresponding to new page visit. + expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) + expect(secondInitST.request.query.ptid).not.toEqual(initSTReceived.request.query.ptid) + await expect(getTraceInfo()).resolves.toEqual([true, MODE.FULL, null]) // session_replay.featAggregate will be null as it's OFF and not imported on subsequent pages + }) + }) +}) diff --git a/tools/testing-server/routes/bam-apis.js b/tools/testing-server/routes/bam-apis.js index 2f73336f1..7cf981f75 100644 --- a/tools/testing-server/routes/bam-apis.js +++ b/tools/testing-server/routes/bam-apis.js @@ -1,4 +1,5 @@ const fp = require('fastify-plugin') +const { v4: uuidv4 } = require('uuid') const { rumFlags } = require('../constants') /** @@ -91,7 +92,7 @@ module.exports = fp(async function (fastify) { } // This endpoint must reply with some text in the body or further resource harvests will be disabled - return reply.code(200).send('123-456') + return reply.code(200).send(uuidv4()) } }) }) From aec34f2767b44650cd473a230f56652317ab6c82 Mon Sep 17 00:00:00 2001 From: Chunwai Li Date: Wed, 28 Jun 2023 10:59:16 -0500 Subject: [PATCH 04/14] replay feature on --- package-lock.json | 12 +- tests/specs/stn/with-session-replay.e2e.js | 142 ++++++++++++++------- 2 files changed, 105 insertions(+), 49 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef7f70f55..1f0b7baa9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9687,9 +9687,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001508", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001508.tgz", - "integrity": "sha512-sdQZOJdmt3GJs1UMNpCCCyeuS2IEGLXnHyAo9yIO5JJDjbjoVRij4M1qep6P6gFpptD1PqIYgzM+gwJbOi92mw==", + "version": "1.0.30001509", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001509.tgz", + "integrity": "sha512-2uDDk+TRiTX5hMcUYT/7CSyzMZxjfGu0vAUjS2g0LSD8UoXOv0LtpH4LxGMemsiPq6LCVIUjNwVM0erkOkGCDA==", "dev": true, "funding": [ { @@ -34042,9 +34042,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001508", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001508.tgz", - "integrity": "sha512-sdQZOJdmt3GJs1UMNpCCCyeuS2IEGLXnHyAo9yIO5JJDjbjoVRij4M1qep6P6gFpptD1PqIYgzM+gwJbOi92mw==", + "version": "1.0.30001509", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001509.tgz", + "integrity": "sha512-2uDDk+TRiTX5hMcUYT/7CSyzMZxjfGu0vAUjS2g0LSD8UoXOv0LtpH4LxGMemsiPq6LCVIUjNwVM0erkOkGCDA==", "dev": true }, "capital-case": { diff --git a/tests/specs/stn/with-session-replay.e2e.js b/tests/specs/stn/with-session-replay.e2e.js index 3404014dc..99ad5c6db 100644 --- a/tests/specs/stn/with-session-replay.e2e.js +++ b/tests/specs/stn/with-session-replay.e2e.js @@ -1,5 +1,6 @@ import { testRumRequest } from '../../../tools/testing-server/utils/expect-tests' import { config, MODE } from '../session-replay/helpers' +import { notIE } from '../../../tools/browser-matcher/common-matchers.mjs' [ ['session tracking is disabled', false], @@ -49,38 +50,33 @@ import { config, MODE } from '../session-replay/helpers' }) }) -describe('Trace behavior when session tracking enabled and replay entitlement is 1', () => { - const rumFlags = { - stn: 1, - err: 1, - ins: 1, - spa: 1, - sr: 1, - loaded: 1 - } - // let getUrlString +describe('Trace when replay entitlement is 1 and stn is 1', () => { let initSTReceived beforeEach(async () => { await browser.destroyAgentSession() + await browser.testHandle.scheduleReply('bamServer', { + test: testRumRequest, + permanent: true, // note this is set since the tests in this block also tests subsequent load behavior + body: JSON.stringify({ + stn: 1, + err: 1, + ins: 1, + spa: 1, + sr: 1, + loaded: 1 + }) + }) + initSTReceived = undefined browser.testHandle.expectResources().then(resPayload => initSTReceived = resPayload) - // getUrlString = browser.testHandle.assetURL('stn/instrumented.html', config({ - // session_trace: { harvestTimeSeconds: 5 } - // })) }) afterEach(async () => { await browser.testHandle.clearScheduledReplies('bamServer') }) it('still runs when replay feature is missing', async () => { - await browser.testHandle.scheduleReply('bamServer', { - test: testRumRequest, - permanent: true, - body: JSON.stringify(rumFlags) - }) - const urlWithoutReplay = await browser.testHandle.assetURL('stn/instrumented.html', { init: { privacy: { cookies_enabled: true } } }) - const getTraceInfo = () => browser.execute(function () { + const getTraceValues = () => browser.execute(function () { return [ Object.values(newrelic.initializedAgents)[0].features.session_trace.featAggregate.isStandalone, Object.values(newrelic.initializedAgents)[0].runtime.session.state.sessionTraceMode @@ -89,44 +85,104 @@ describe('Trace behavior when session tracking enabled and replay entitlement is await browser.url(urlWithoutReplay).then(() => browser.waitForAgentLoad()).then(async () => { expect(initSTReceived).toBeTruthy() // that is, trace should still fully run when the replay feature isn't around - await expect(getTraceInfo()).resolves.toEqual([true, MODE.FULL]) + await expect(getTraceValues()).resolves.toEqual([true, MODE.FULL]) return Promise.all([browser.testHandle.expectResources(3000), browser.refresh().then(() => browser.waitForAgentLoad())]) }).then(async ([secondInitST]) => { // On subsequent page load or refresh, trace should maintain the set mode, standalone, and same sessionid but have a new ptid corresponding to new page visit. expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) expect(secondInitST.request.query.ptid).not.toEqual(initSTReceived.request.query.ptid) - await expect(getTraceInfo()).resolves.toEqual([true, MODE.FULL]) // note it's expected & assumed that the replay mode is OFF + await expect(getTraceValues()).resolves.toEqual([true, MODE.FULL]) // note it's expected & assumed that the replay mode is OFF }) }) - it('still runs when replay feature is present but disabled', async () => { + ;[ + ['OFF', { sampleRate: 0, errorSampleRate: 0 }], + ['FULL', { sampleRate: 1, errorSampleRate: 0 }], + ['ERR', { sampleRate: 0, errorSampleRate: 1 }] + ].forEach(([replayMode, replayConfig]) => { + it.withBrowsersMatching(notIE)(`runs in full when replay feature is present and in ${replayMode} mode`, async () => { + const urlWithReplay = await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: replayConfig })) + const getRuntimeValues = () => browser.execute(function () { + const agent = Object.values(newrelic.initializedAgents)[0] + return [ + agent.features.session_trace.featAggregate.isStandalone, + agent.runtime.session.state.sessionTraceMode, + agent.features.session_replay.featAggregate?.initialized // expect replay to be fully imported and intialized, but in OFF mode per config above, via isStandalone = true + ] + }) + + await browser.url(urlWithReplay).then(() => browser.waitForAgentLoad()).then(async () => { + expect(initSTReceived).toBeTruthy() // that is, trace should still fully run when the replay feature isn't around + + if (replayMode === 'OFF') await expect(getRuntimeValues()).resolves.toEqual([true, MODE.FULL, true]) + else await expect(getRuntimeValues()).resolves.toEqual([false, MODE.FULL, true]) // when replay is running, trace is no longer op in standalone mode + + return Promise.all([browser.testHandle.expectResources(3000), browser.refresh().then(() => browser.waitForAgentLoad())]) + }).then(async ([secondInitST]) => { + // On subsequent page load or refresh, trace should maintain the set mode, standalone, and same sessionid but have a new ptid corresponding to new page visit. + expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) + expect(secondInitST.request.query.ptid).not.toEqual(initSTReceived.request.query.ptid) + + if (replayMode === 'OFF') { + await expect(getRuntimeValues()).resolves.toEqual([true, MODE.FULL, null]) // session_replay.featAggregate will be null as it's OFF and not imported on subsequent pages + } else { + await expect(getRuntimeValues()).resolves.toEqual([false, MODE.FULL, true]) + } + }) + }) + }) +}) + +describe.withBrowsersMatching(notIE)('Trace when replay entitlement is 1 and stn is 0', () => { + let initSTReceived + beforeEach(async () => { + await browser.destroyAgentSession() await browser.testHandle.scheduleReply('bamServer', { test: testRumRequest, - permanent: true, - body: JSON.stringify(rumFlags) + body: JSON.stringify({ + stn: 0, + err: 1, + ins: 1, + spa: 1, + sr: 1, + loaded: 1 + }) }) - const urlWithoutReplay = await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: { sampleRate: 0, errorSampleRate: 0 } })) - const getTraceInfo = () => browser.execute(function () { - const agent = Object.values(newrelic.initializedAgents)[0] - return [ - agent.features.session_trace.featAggregate.isStandalone, - agent.runtime.session.state.sessionTraceMode, - agent.features.session_replay.featAggregate?.initialized // expect replay to be fully imported and intialized, but in OFF mode per config above, via isStandalone = true - ] - }) + initSTReceived = undefined + browser.testHandle.expectResources().then(resPayload => initSTReceived = resPayload) + }) - await browser.url(urlWithoutReplay).then(() => browser.waitForAgentLoad()).then(async () => { - expect(initSTReceived).toBeTruthy() // that is, trace should still fully run when the replay feature isn't around - await expect(getTraceInfo()).resolves.toEqual([true, MODE.FULL, true]) + it('does not run when replay is OFF', async () => { + const urlReplayOff = await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: { sampleRate: 0, errorSampleRate: 0 } })) + await browser.url(urlReplayOff).then(() => browser.waitForAgentLoad()).then(() => { + expect(initSTReceived).toBeUndefined() + }) + }) - return Promise.all([browser.testHandle.expectResources(3000), browser.refresh().then(() => browser.waitForAgentLoad())]) - }).then(async ([secondInitST]) => { - // On subsequent page load or refresh, trace should maintain the set mode, standalone, and same sessionid but have a new ptid corresponding to new page visit. - expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) - expect(secondInitST.request.query.ptid).not.toEqual(initSTReceived.request.query.ptid) - await expect(getTraceInfo()).resolves.toEqual([true, MODE.FULL, null]) // session_replay.featAggregate will be null as it's OFF and not imported on subsequent pages + ;[ + ['FULL', { sampleRate: 1, errorSampleRate: 0 }], + ['ERR', { sampleRate: 0, errorSampleRate: 1 }] + ].forEach(([replayMode, replayConfig]) => { + it(`still runs and in the same ${replayMode} mode as replay feature that's on`, async () => { + const urlReplayOn = await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: replayConfig })) + await browser.url(urlReplayOn).then(() => browser.waitForAgentLoad()).then(async () => { + const getAssumedValues = browser.execute(function () { + const agent = Object.values(newrelic.initializedAgents)[0] + return [ + agent.features.session_trace.featAggregate.isStandalone, + agent.runtime.session.state.sessionTraceMode + ] + }) + if (replayMode === 'FULL') { + await expect(getAssumedValues).resolves.toEqual([false, MODE.FULL]) + expect(initSTReceived).toBeTruthy() + } else if (replayMode === 'ERR') { + await expect(getAssumedValues).resolves.toEqual([false, MODE.ERROR]) + expect(initSTReceived).toBeUndefined() // trace in error mode is not expected to send anything on startup + } + }) }) }) }) From 26533c5b15515a1b57f861876c60780e76c6c08f Mon Sep 17 00:00:00 2001 From: Chunwai Li Date: Wed, 28 Jun 2023 15:01:39 -0500 Subject: [PATCH 05/14] fine tuning with-session-replay --- tests/specs/stn/with-session-replay.e2e.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/specs/stn/with-session-replay.e2e.js b/tests/specs/stn/with-session-replay.e2e.js index 99ad5c6db..c0554f76b 100644 --- a/tests/specs/stn/with-session-replay.e2e.js +++ b/tests/specs/stn/with-session-replay.e2e.js @@ -1,3 +1,8 @@ +/* + * The top half of this file tests for previous standalone Trace feature behavior in the absence of replay flag from RUM or feature in agent build. + * The bottom half tests the truth table defined in docs related to NR-137369 around how trace behaves in the prescence of replay feature. + */ + import { testRumRequest } from '../../../tools/testing-server/utils/expect-tests' import { config, MODE } from '../session-replay/helpers' import { notIE } from '../../../tools/browser-matcher/common-matchers.mjs' @@ -74,8 +79,8 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { await browser.testHandle.clearScheduledReplies('bamServer') }) - it('still runs when replay feature is missing', async () => { - const urlWithoutReplay = await browser.testHandle.assetURL('stn/instrumented.html', { init: { privacy: { cookies_enabled: true } } }) + it('still runs when replay feature is missing or disabled', async () => { + const urlWithoutReplay = await browser.testHandle.assetURL('stn/instrumented.html', { init: { privacy: { cookies_enabled: true }, session_replay: { enabled: false } } }) const getTraceValues = () => browser.execute(function () { return [ Object.values(newrelic.initializedAgents)[0].features.session_trace.featAggregate.isStandalone, @@ -166,7 +171,7 @@ describe.withBrowsersMatching(notIE)('Trace when replay entitlement is 1 and stn ['ERR', { sampleRate: 0, errorSampleRate: 1 }] ].forEach(([replayMode, replayConfig]) => { it(`still runs and in the same ${replayMode} mode as replay feature that's on`, async () => { - const urlReplayOn = await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: replayConfig })) + const urlReplayOn = await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: replayConfig, session_trace: { harvestTimeSeconds: 2 } })) await browser.url(urlReplayOn).then(() => browser.waitForAgentLoad()).then(async () => { const getAssumedValues = browser.execute(function () { const agent = Object.values(newrelic.initializedAgents)[0] @@ -178,6 +183,11 @@ describe.withBrowsersMatching(notIE)('Trace when replay entitlement is 1 and stn if (replayMode === 'FULL') { await expect(getAssumedValues).resolves.toEqual([false, MODE.FULL]) expect(initSTReceived).toBeTruthy() + + // When not in standalone, trace bypasses the old rate limiting of only harvesting on 30+ nodes. In practice, we should get few-secs-span harvests without that threshold. + const second = await browser.testHandle.expectResources(3000).then(payload => payload.request.body.res.length) // 2nd harvest is usually riddled with a bunch of startup resource nodes + const third = await browser.testHandle.expectResources(3000).then(payload => payload.request.body.res.length) + expect([second, third].some(length => length < 30)).toBeTruthy() } else if (replayMode === 'ERR') { await expect(getAssumedValues).resolves.toEqual([false, MODE.ERROR]) expect(initSTReceived).toBeUndefined() // trace in error mode is not expected to send anything on startup From 6445a65ba7a5e2686b52f069ba4ba123c397232f Mon Sep 17 00:00:00 2001 From: Chunwai Li Date: Thu, 29 Jun 2023 10:36:49 -0500 Subject: [PATCH 06/14] stabilize flaky replay test --- tests/specs/stn/with-session-replay.e2e.js | 68 ++++++++++++++-------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/tests/specs/stn/with-session-replay.e2e.js b/tests/specs/stn/with-session-replay.e2e.js index c0554f76b..c946b1caf 100644 --- a/tests/specs/stn/with-session-replay.e2e.js +++ b/tests/specs/stn/with-session-replay.e2e.js @@ -82,22 +82,30 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { it('still runs when replay feature is missing or disabled', async () => { const urlWithoutReplay = await browser.testHandle.assetURL('stn/instrumented.html', { init: { privacy: { cookies_enabled: true }, session_replay: { enabled: false } } }) const getTraceValues = () => browser.execute(function () { + const agent = Object.values(newrelic.initializedAgents)[0] return [ - Object.values(newrelic.initializedAgents)[0].features.session_trace.featAggregate.isStandalone, - Object.values(newrelic.initializedAgents)[0].runtime.session.state.sessionTraceMode + agent.features.session_trace.featAggregate.isStandalone, + agent.runtime.session.state.sessionTraceMode, + agent.runtime.ptid ] }) + let firstPageAgentVals, secondPageAgentVals await browser.url(urlWithoutReplay).then(() => browser.waitForAgentLoad()).then(async () => { + firstPageAgentVals = await getTraceValues() expect(initSTReceived).toBeTruthy() // that is, trace should still fully run when the replay feature isn't around - await expect(getTraceValues()).resolves.toEqual([true, MODE.FULL]) + expect(initSTReceived.request.query.ptid).toBeUndefined() // trace doesn't have ptid on first initial harvest + expect(firstPageAgentVals).toEqual([true, MODE.FULL, expect.any(String)]) - return Promise.all([browser.testHandle.expectResources(3000), browser.refresh().then(() => browser.waitForAgentLoad())]) - }).then(async ([secondInitST]) => { + return browser.url(await browser.testHandle.assetURL('/')).then(() => browser.url(urlWithoutReplay)) + }).then(() => Promise.all([browser.testHandle.expectResources(3000), browser.waitForAgentLoad()])).then(async ([secondInitST]) => { + secondPageAgentVals = await getTraceValues() // On subsequent page load or refresh, trace should maintain the set mode, standalone, and same sessionid but have a new ptid corresponding to new page visit. expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) - expect(secondInitST.request.query.ptid).not.toEqual(initSTReceived.request.query.ptid) - await expect(getTraceValues()).resolves.toEqual([true, MODE.FULL]) // note it's expected & assumed that the replay mode is OFF + expect(secondInitST.request.query.ptid).toBeUndefined() + expect(secondPageAgentVals).toEqual([true, MODE.FULL, expect.any(String)]) // note it's expected & assumed that the replay mode is OFF + + expect(secondPageAgentVals[2]).not.toEqual(firstPageAgentVals[2]) // ptids }) }) @@ -117,22 +125,26 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { ] }) + let firstPageAgentVals, secondPageAgentVals await browser.url(urlWithReplay).then(() => browser.waitForAgentLoad()).then(async () => { - expect(initSTReceived).toBeTruthy() // that is, trace should still fully run when the replay feature isn't around + firstPageAgentVals = await getRuntimeValues() + expect(initSTReceived).toBeTruthy() + expect(initSTReceived.request.query.ptid).toBeUndefined() - if (replayMode === 'OFF') await expect(getRuntimeValues()).resolves.toEqual([true, MODE.FULL, true]) - else await expect(getRuntimeValues()).resolves.toEqual([false, MODE.FULL, true]) // when replay is running, trace is no longer op in standalone mode + if (replayMode === 'OFF') expect(firstPageAgentVals).toEqual([true, MODE.FULL, true]) + else expect(firstPageAgentVals).toEqual([false, MODE.FULL, true]) // when replay is running, trace is no longer op in standalone mode - return Promise.all([browser.testHandle.expectResources(3000), browser.refresh().then(() => browser.waitForAgentLoad())]) - }).then(async ([secondInitST]) => { - // On subsequent page load or refresh, trace should maintain the set mode, standalone, and same sessionid but have a new ptid corresponding to new page visit. + return browser.url(await browser.testHandle.assetURL('/')).then(() => browser.url(urlWithReplay)) + }).then(() => Promise.all([browser.testHandle.expectResources(3000), browser.waitForAgentLoad()])).then(async ([secondInitST]) => { + secondPageAgentVals = await getRuntimeValues() + // On subsequent page load or refresh, trace should maintain FULL mode and session id. expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) - expect(secondInitST.request.query.ptid).not.toEqual(initSTReceived.request.query.ptid) + expect(secondInitST.request.query.ptid).toBeUndefined() // this validates we're actually getting the 2nd page's initial res, not 1st page's unload res if (replayMode === 'OFF') { - await expect(getRuntimeValues()).resolves.toEqual([true, MODE.FULL, null]) // session_replay.featAggregate will be null as it's OFF and not imported on subsequent pages + expect(secondPageAgentVals).toEqual([true, MODE.FULL, null]) // session_replay.featAggregate will be null as it's OFF and not imported on subsequent pages } else { - await expect(getRuntimeValues()).resolves.toEqual([false, MODE.FULL, true]) + expect(secondPageAgentVals).toEqual([false, MODE.FULL, true]) } }) }) @@ -145,6 +157,7 @@ describe.withBrowsersMatching(notIE)('Trace when replay entitlement is 1 and stn await browser.destroyAgentSession() await browser.testHandle.scheduleReply('bamServer', { test: testRumRequest, + permanent: true, body: JSON.stringify({ stn: 0, err: 1, @@ -172,16 +185,17 @@ describe.withBrowsersMatching(notIE)('Trace when replay entitlement is 1 and stn ].forEach(([replayMode, replayConfig]) => { it(`still runs and in the same ${replayMode} mode as replay feature that's on`, async () => { const urlReplayOn = await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: replayConfig, session_trace: { harvestTimeSeconds: 2 } })) + const getAssumedValues = () => browser.execute(function () { + const agent = Object.values(newrelic.initializedAgents)[0] + return [ + agent.features.session_trace.featAggregate.isStandalone, + agent.runtime.session.state.sessionTraceMode + ] + }) + await browser.url(urlReplayOn).then(() => browser.waitForAgentLoad()).then(async () => { - const getAssumedValues = browser.execute(function () { - const agent = Object.values(newrelic.initializedAgents)[0] - return [ - agent.features.session_trace.featAggregate.isStandalone, - agent.runtime.session.state.sessionTraceMode - ] - }) if (replayMode === 'FULL') { - await expect(getAssumedValues).resolves.toEqual([false, MODE.FULL]) + await expect(getAssumedValues()).resolves.toEqual([false, MODE.FULL]) expect(initSTReceived).toBeTruthy() // When not in standalone, trace bypasses the old rate limiting of only harvesting on 30+ nodes. In practice, we should get few-secs-span harvests without that threshold. @@ -189,9 +203,13 @@ describe.withBrowsersMatching(notIE)('Trace when replay entitlement is 1 and stn const third = await browser.testHandle.expectResources(3000).then(payload => payload.request.body.res.length) expect([second, third].some(length => length < 30)).toBeTruthy() } else if (replayMode === 'ERR') { - await expect(getAssumedValues).resolves.toEqual([false, MODE.ERROR]) + await expect(getAssumedValues()).resolves.toEqual([false, MODE.ERROR]) expect(initSTReceived).toBeUndefined() // trace in error mode is not expected to send anything on startup } + + return browser.refresh().then(() => browser.waitForAgentLoad()) + }).then(async () => { + await expect(getAssumedValues()).resolves.toEqual([false, replayMode === 'FULL' ? MODE.FULL : MODE.ERROR]) // page loads of existing session should use same trace mode even if stn = 0 }) }) }) From 3270aa587e70bb9348a9c62494f54fc73d09c181 Mon Sep 17 00:00:00 2001 From: Chunwai Li Date: Thu, 29 Jun 2023 16:47:08 -0500 Subject: [PATCH 07/14] Add error mode testing for trace --- tests/specs/stn/with-replay-error-mode.e2e.js | 99 +++++++++++++++++++ tools/browser-matcher/common-matchers.mjs | 3 + 2 files changed, 102 insertions(+) create mode 100644 tests/specs/stn/with-replay-error-mode.e2e.js diff --git a/tests/specs/stn/with-replay-error-mode.e2e.js b/tests/specs/stn/with-replay-error-mode.e2e.js new file mode 100644 index 000000000..7c2d6c5f5 --- /dev/null +++ b/tests/specs/stn/with-replay-error-mode.e2e.js @@ -0,0 +1,99 @@ +/* + * All behavior and mode transition from error mode of Trace in tandem with the replay feature is tested in here. + * Right now, Trace can only be in error mode when its stn flag is 0 but replay runs in error mode. + */ +import { testRumRequest } from '../../../tools/testing-server/utils/expect-tests' +import { config, MODE } from '../session-replay/helpers' +import { notIE, onlyChrome, supportsMultipleTabs } from '../../../tools/browser-matcher/common-matchers.mjs' + +describe.withBrowsersMatching(notIE)('Trace error mode', () => { + let initSTReceived, getReplayOnErrorUrl + beforeEach(async () => { + await browser.destroyAgentSession() + await browser.testHandle.scheduleReply('bamServer', { + test: testRumRequest, + permanent: true, + body: JSON.stringify({ stn: 0, err: 1, ins: 1, spa: 1, sr: 1, loaded: 1 }) + }) + + initSTReceived = undefined + browser.testHandle.expectResources().then(resPayload => initSTReceived = resPayload) + getReplayOnErrorUrl = browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: { sampleRate: 0, errorSampleRate: 1 }, session_trace: { harvestTimeSeconds: 2 } })) + }) + + const getTraceMode = () => browser.execute(function () { + const agent = Object.values(newrelic.initializedAgents)[0] + return [ + agent.runtime.session.state.sessionTraceMode, + agent.features.session_trace.featAggregate.isStandalone + ] + }) + function simulateErrorInBrowser () { // this is a way to throw error in WdIO / Selenium without killing the test itself + const errorElem = document.createElement('script') + errorElem.textContent = 'throw new Error("triggered! 0__0");' + document.body.append(errorElem) + } + + it('switches to full mode when an error happens after page load', async () => { + await getReplayOnErrorUrl.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(async () => { + let startingMode = (await getTraceMode())[0] + expect(initSTReceived).toBeUndefined() + expect(startingMode).toEqual(MODE.ERROR) + + await browser.execute(simulateErrorInBrowser) + let modeAfterErr = (await getTraceMode())[0] + expect(modeAfterErr).toEqual(MODE.FULL) + // The loadEventEnd is part of PT and PNT resource entry and is a node created at start of page life. + expect(initSTReceived.request.body.res.find(node => node.n === 'loadEventEnd')).toEqual(expect.objectContaining({ n: 'loadEventEnd', o: 'document', t: 'timing' })) + + await expect(browser.testHandle.expectResources(3000)).resolves.toBeTruthy() // double check there's nothing wrong with full mdoe interval harvest + }) + }) + + it('starts in full mode when an error happens before page load', async () => { + await getReplayOnErrorUrl.then(builtUrl => browser.url(builtUrl)).then(() => browser.execute(simulateErrorInBrowser)).then(() => browser.waitForAgentLoad()).then(async () => { + let startingMode = (await getTraceMode())[0] + expect(initSTReceived).toBeTruthy() + expect(startingMode).toEqual(MODE.FULL) + + await expect(browser.testHandle.expectResources(3000)).resolves.toBeTruthy() + }) + }) + + it.withBrowsersMatching(onlyChrome)('does not capture more than the last 30 seconds when error happens', async () => { + await getReplayOnErrorUrl.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(async () => { + await browser.pause(30000) + await Promise.all([ + browser.testHandle.expectResources(3000), // the setup expectRes promise would've timed out already -- you will see some console errors but they don't impact validity + browser.execute(simulateErrorInBrowser) + ]).then(async ([initSTAfterErr]) => { + expect(initSTAfterErr.request.body.res.find(node => node.n === 'loadEventEnd')).toBeUndefined() // that node should've been tossed out by now + }) + }) + }) + + it('does not perform final harvest while in this mode', async () => { + await getReplayOnErrorUrl.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(async () => { + await getTraceMode().then(traceState => expect(traceState).toEqual([MODE.ERROR, false])) // sanity check tbh + expect(initSTReceived).toBeUndefined() + }).then(() => browser.refresh()).then(() => browser.waitForAgentLoad()).then(async () => { + await getTraceMode().then(traceState => expect(traceState).toEqual([MODE.ERROR, false])) + expect(initSTReceived).toBeUndefined() // no harvest from either previous unload or from new existing-session load + }) + }) + + it.withBrowsersMatching(supportsMultipleTabs)('catches mode transition from other pages in the session', async () => { + await getReplayOnErrorUrl.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(async () => { + await getTraceMode().then(([traceMode]) => expect(traceMode).toEqual(MODE.ERROR)) + let firstPageTitle = await browser.getTitle() + + await browser.newWindow(await getReplayOnErrorUrl, { windowName: 'Second page' }) + await getTraceMode().then(([traceMode]) => expect(traceMode).toEqual(MODE.ERROR)) + await browser.execute(simulateErrorInBrowser) + await getTraceMode().then(([traceMode]) => expect(traceMode).toEqual(MODE.FULL)) + + await browser.switchWindow(firstPageTitle) + await getTraceMode().then(([traceMode]) => expect(traceMode).toEqual(MODE.FULL)) + }) + }) +}) diff --git a/tools/browser-matcher/common-matchers.mjs b/tools/browser-matcher/common-matchers.mjs index 5b3595aef..57b79ed9d 100644 --- a/tools/browser-matcher/common-matchers.mjs +++ b/tools/browser-matcher/common-matchers.mjs @@ -56,3 +56,6 @@ export const notSafari = new SpecMatcher() .include('firefox') .include('ios') .include('android') + +export const onlyChrome = new SpecMatcher() + .include('chrome') From 91269c1ae41468656c0b870907d50c270f7f148e Mon Sep 17 00:00:00 2001 From: Chunwai Li Date: Thu, 29 Jun 2023 18:26:48 -0500 Subject: [PATCH 08/14] finish error mode tests -- v flaky atm --- src/features/session_trace/aggregate/index.js | 2 +- tests/specs/stn/with-replay-error-mode.e2e.js | 63 ++++++++++++++++--- tests/specs/stn/with-session-replay.e2e.js | 10 +-- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/src/features/session_trace/aggregate/index.js b/src/features/session_trace/aggregate/index.js index 88f1f1ee3..27e17320c 100644 --- a/src/features/session_trace/aggregate/index.js +++ b/src/features/session_trace/aggregate/index.js @@ -97,8 +97,8 @@ export class Aggregate extends AggregateBase { const stopTracePerm = () => { if (sessionEntity.state.sessionTraceMode !== MODE.OFF) sessionEntity.write({ sessionTraceMode: MODE.OFF }) operationalGate.permanentlyDecide(false) - this.#scheduler?.stopTimer(true) if (mostRecentModeKnown === MODE.FULL) this.#scheduler?.runHarvest() // allow queued nodes (past opGate) to final harvest, unless they were buffered in other modes + this.#scheduler?.stopTimer(true) // the 'true' arg here will forcibly block any future call to runHarvest, so the last runHarvest above must be prior this.#scheduler = null } diff --git a/tests/specs/stn/with-replay-error-mode.e2e.js b/tests/specs/stn/with-replay-error-mode.e2e.js index 7c2d6c5f5..af1388acb 100644 --- a/tests/specs/stn/with-replay-error-mode.e2e.js +++ b/tests/specs/stn/with-replay-error-mode.e2e.js @@ -6,6 +6,14 @@ import { testRumRequest } from '../../../tools/testing-server/utils/expect-tests import { config, MODE } from '../session-replay/helpers' import { notIE, onlyChrome, supportsMultipleTabs } from '../../../tools/browser-matcher/common-matchers.mjs' +const getTraceMode = () => browser.execute(function () { + const agent = Object.values(newrelic.initializedAgents)[0] + return [ + agent.runtime.session.state.sessionTraceMode, + agent.features.session_trace.featAggregate.isStandalone + ] +}) + describe.withBrowsersMatching(notIE)('Trace error mode', () => { let initSTReceived, getReplayOnErrorUrl beforeEach(async () => { @@ -21,13 +29,6 @@ describe.withBrowsersMatching(notIE)('Trace error mode', () => { getReplayOnErrorUrl = browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: { sampleRate: 0, errorSampleRate: 1 }, session_trace: { harvestTimeSeconds: 2 } })) }) - const getTraceMode = () => browser.execute(function () { - const agent = Object.values(newrelic.initializedAgents)[0] - return [ - agent.runtime.session.state.sessionTraceMode, - agent.features.session_trace.featAggregate.isStandalone - ] - }) function simulateErrorInBrowser () { // this is a way to throw error in WdIO / Selenium without killing the test itself const errorElem = document.createElement('script') errorElem.textContent = 'throw new Error("triggered! 0__0");' @@ -91,9 +92,57 @@ describe.withBrowsersMatching(notIE)('Trace error mode', () => { await getTraceMode().then(([traceMode]) => expect(traceMode).toEqual(MODE.ERROR)) await browser.execute(simulateErrorInBrowser) await getTraceMode().then(([traceMode]) => expect(traceMode).toEqual(MODE.FULL)) + // NOTE: replay entitlement must be on (sr = 1) for trace to exhibit this behavior, although this could change in the future to be applicable to standalone trace in a session. await browser.switchWindow(firstPageTitle) await getTraceMode().then(([traceMode]) => expect(traceMode).toEqual(MODE.FULL)) }) }) + + /* The mode transition should also work even when replay entitlement is 0 or it is off and trace is standalone, as long as session tracking is enabled. + However, this particular behavior currently does not need testing because trace can not yet end up in error mode when running by itself. Could change in the future. */ +}) + +describe.withBrowsersMatching(notIE)('Trace when replay runs then is aborted', () => { + let initSTReceived + beforeEach(async () => { + await browser.destroyAgentSession() + await browser.testHandle.scheduleReply('bamServer', { + test: testRumRequest, + permanent: true, + body: JSON.stringify({ stn: 0, err: 1, ins: 1, spa: 1, sr: 1, loaded: 1 }) + }) + + initSTReceived = undefined + browser.testHandle.expectResources().then(resPayload => initSTReceived = resPayload) + }) + + const triggerReplayAbort = () => browser.execute(function () { Object.values(NREUM.initializedAgents)[0].runtime.session.reset() }) + + ;[ + ['does a last harvest then stops, in full mode', MODE.FULL, { sampleRate: 1, errorSampleRate: 0 }], + ['does not harvest anything, in error mode', MODE.ERROR, { sampleRate: 0, errorSampleRate: 1 }] + ].forEach(([description, supposedMode, replayConfig]) => { + it(description, async () => { + let url = await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: replayConfig, session_trace: { harvestTimeSeconds: 2 } })) + await browser.url(url) + await browser.waitForAgentLoad() + expect(await getTraceMode()).toEqual([supposedMode, false]) + if (supposedMode === MODE.FULL) expect(initSTReceived).toBeTruthy() + else expect(initSTReceived).toBeUndefined() // in ERROR mode + + let lastSTHarvest = browser.testHandle.expectResources(1000) // abort should cause a harvest right away (in FULL), rather than the usual interval + await triggerReplayAbort() + expect(await getTraceMode()).toEqual([MODE.OFF, false]) + if (supposedMode === MODE.FULL) await expect(lastSTHarvest).resolves.toBeTruthy() + else await expect(lastSTHarvest).rejects.toThrow() + + let anotherTraceHarvest = browser.testHandle.expectResources(3000, true) + await expect(anotherTraceHarvest).resolves.toBeUndefined() // we shouldn't see any more harvest after the previous one on abort + + anotherTraceHarvest = browser.testHandle.expectResources(2000, true) + await browser.url(await browser.testHandle.assetURL('/')) + await expect(anotherTraceHarvest).resolves.toBeUndefined() // doubly check that nothing else is sent, i.e. on test page's unload logic + }) + }) }) diff --git a/tests/specs/stn/with-session-replay.e2e.js b/tests/specs/stn/with-session-replay.e2e.js index c946b1caf..5e9e161de 100644 --- a/tests/specs/stn/with-session-replay.e2e.js +++ b/tests/specs/stn/with-session-replay.e2e.js @@ -97,8 +97,9 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { expect(initSTReceived.request.query.ptid).toBeUndefined() // trace doesn't have ptid on first initial harvest expect(firstPageAgentVals).toEqual([true, MODE.FULL, expect.any(String)]) - return browser.url(await browser.testHandle.assetURL('/')).then(() => browser.url(urlWithoutReplay)) - }).then(() => Promise.all([browser.testHandle.expectResources(3000), browser.waitForAgentLoad()])).then(async ([secondInitST]) => { + return browser.url(await browser.testHandle.assetURL('/')).then(() => browser.pause(500)).then(() => // this page transition expecting the right trace is flaky, so pause 500 + Promise.all([browser.testHandle.expectResources(5000), browser.url(urlWithoutReplay).then(() => browser.waitForAgentLoad())])) + }).then(async ([secondInitST]) => { secondPageAgentVals = await getTraceValues() // On subsequent page load or refresh, trace should maintain the set mode, standalone, and same sessionid but have a new ptid corresponding to new page visit. expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) @@ -134,8 +135,9 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { if (replayMode === 'OFF') expect(firstPageAgentVals).toEqual([true, MODE.FULL, true]) else expect(firstPageAgentVals).toEqual([false, MODE.FULL, true]) // when replay is running, trace is no longer op in standalone mode - return browser.url(await browser.testHandle.assetURL('/')).then(() => browser.url(urlWithReplay)) - }).then(() => Promise.all([browser.testHandle.expectResources(3000), browser.waitForAgentLoad()])).then(async ([secondInitST]) => { + return browser.url(await browser.testHandle.assetURL('/')).then(() => browser.pause(500)).then(() => // this page transition expecting the right trace is flaky, so pause 500 + Promise.all([browser.testHandle.expectResources(5000), browser.url(urlWithReplay).then(() => browser.waitForAgentLoad())])) + }).then(async ([secondInitST]) => { secondPageAgentVals = await getRuntimeValues() // On subsequent page load or refresh, trace should maintain FULL mode and session id. expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) From 5da49f7fac25e41845912e20d31b9f1e15a082a0 Mon Sep 17 00:00:00 2001 From: Chunwai Li Date: Thu, 29 Jun 2023 19:07:18 -0500 Subject: [PATCH 09/14] trying to fix this bs safari flake --- tests/specs/stn/with-session-replay.e2e.js | 40 ++++++++++++---------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/tests/specs/stn/with-session-replay.e2e.js b/tests/specs/stn/with-session-replay.e2e.js index 5e9e161de..7430faba1 100644 --- a/tests/specs/stn/with-session-replay.e2e.js +++ b/tests/specs/stn/with-session-replay.e2e.js @@ -91,15 +91,17 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { }) let firstPageAgentVals, secondPageAgentVals - await browser.url(urlWithoutReplay).then(() => browser.waitForAgentLoad()).then(async () => { - firstPageAgentVals = await getTraceValues() - expect(initSTReceived).toBeTruthy() // that is, trace should still fully run when the replay feature isn't around - expect(initSTReceived.request.query.ptid).toBeUndefined() // trace doesn't have ptid on first initial harvest - expect(firstPageAgentVals).toEqual([true, MODE.FULL, expect.any(String)]) - - return browser.url(await browser.testHandle.assetURL('/')).then(() => browser.pause(500)).then(() => // this page transition expecting the right trace is flaky, so pause 500 - Promise.all([browser.testHandle.expectResources(5000), browser.url(urlWithoutReplay).then(() => browser.waitForAgentLoad())])) - }).then(async ([secondInitST]) => { + await browser.url(urlWithoutReplay).then(() => browser.waitForAgentLoad()) + firstPageAgentVals = await getTraceValues() + expect(initSTReceived).toBeTruthy() // that is, trace should still fully run when the replay feature isn't around + expect(initSTReceived.request.query.ptid).toBeUndefined() // trace doesn't have ptid on first initial harvest + expect(firstPageAgentVals).toEqual([true, MODE.FULL, expect.any(String)]) + + await browser.url(await browser.testHandle.assetURL('/')) + + let anotherInitST = browser.testHandle.expectResources(3000) + await browser.url(urlWithoutReplay).then(() => browser.waitForAgentLoad()) + await anotherInitST.then(async (secondInitST) => { secondPageAgentVals = await getTraceValues() // On subsequent page load or refresh, trace should maintain the set mode, standalone, and same sessionid but have a new ptid corresponding to new page visit. expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) @@ -127,17 +129,19 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { }) let firstPageAgentVals, secondPageAgentVals - await browser.url(urlWithReplay).then(() => browser.waitForAgentLoad()).then(async () => { - firstPageAgentVals = await getRuntimeValues() - expect(initSTReceived).toBeTruthy() - expect(initSTReceived.request.query.ptid).toBeUndefined() + await browser.url(urlWithReplay).then(() => browser.waitForAgentLoad()) + firstPageAgentVals = await getRuntimeValues() + expect(initSTReceived).toBeTruthy() + expect(initSTReceived.request.query.ptid).toBeUndefined() + + if (replayMode === 'OFF') expect(firstPageAgentVals).toEqual([true, MODE.FULL, true]) + else expect(firstPageAgentVals).toEqual([false, MODE.FULL, true]) // when replay is running, trace is no longer op in standalone mode - if (replayMode === 'OFF') expect(firstPageAgentVals).toEqual([true, MODE.FULL, true]) - else expect(firstPageAgentVals).toEqual([false, MODE.FULL, true]) // when replay is running, trace is no longer op in standalone mode + await browser.url(await browser.testHandle.assetURL('/')) - return browser.url(await browser.testHandle.assetURL('/')).then(() => browser.pause(500)).then(() => // this page transition expecting the right trace is flaky, so pause 500 - Promise.all([browser.testHandle.expectResources(5000), browser.url(urlWithReplay).then(() => browser.waitForAgentLoad())])) - }).then(async ([secondInitST]) => { + let anotherInitST = browser.testHandle.expectResources(3000) + await browser.url(urlWithReplay).then(() => browser.waitForAgentLoad()) + await anotherInitST.then(async (secondInitST) => { secondPageAgentVals = await getRuntimeValues() // On subsequent page load or refresh, trace should maintain FULL mode and session id. expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) From 423c99daf9ad6b6a1427a70235f197897fb74b8f Mon Sep 17 00:00:00 2001 From: Chunwai Li Date: Fri, 30 Jun 2023 12:49:50 -0500 Subject: [PATCH 10/14] all (xcept saf 15) stable with retry --- tests/specs/stn/with-replay-error-mode.e2e.js | 1 + tests/specs/stn/with-session-replay.e2e.js | 182 ++++++++---------- 2 files changed, 82 insertions(+), 101 deletions(-) diff --git a/tests/specs/stn/with-replay-error-mode.e2e.js b/tests/specs/stn/with-replay-error-mode.e2e.js index af1388acb..041f27650 100644 --- a/tests/specs/stn/with-replay-error-mode.e2e.js +++ b/tests/specs/stn/with-replay-error-mode.e2e.js @@ -91,6 +91,7 @@ describe.withBrowsersMatching(notIE)('Trace error mode', () => { await browser.newWindow(await getReplayOnErrorUrl, { windowName: 'Second page' }) await getTraceMode().then(([traceMode]) => expect(traceMode).toEqual(MODE.ERROR)) await browser.execute(simulateErrorInBrowser) + await browser.pause(500) await getTraceMode().then(([traceMode]) => expect(traceMode).toEqual(MODE.FULL)) // NOTE: replay entitlement must be on (sr = 1) for trace to exhibit this behavior, although this could change in the future to be applicable to standalone trace in a session. diff --git a/tests/specs/stn/with-session-replay.e2e.js b/tests/specs/stn/with-session-replay.e2e.js index 7430faba1..f6411eaf8 100644 --- a/tests/specs/stn/with-session-replay.e2e.js +++ b/tests/specs/stn/with-session-replay.e2e.js @@ -12,72 +12,57 @@ import { notIE } from '../../../tools/browser-matcher/common-matchers.mjs' ['session tracking enabled but replay entitlement is 0', true] ].forEach(([run, trackingOn]) => { describe(`Trace behavior when ${run}`, () => { - let stPayloadReceived, getUrlString + let getUrlString beforeEach(() => { - stPayloadReceived = undefined - browser.testHandle.expectResources().then(() => stPayloadReceived = true) - getUrlString = browser.testHandle.assetURL('stn/instrumented.html', { - init: { - privacy: { cookies_enabled: trackingOn } - } - }) + getUrlString = browser.testHandle.assetURL('stn/instrumented.html', { init: { privacy: { cookies_enabled: trackingOn } } }) }) it('does not run if stn flag is 0', async () => { await browser.testHandle.scheduleReply('bamServer', { test: testRumRequest, - body: JSON.stringify({ - stn: 0, - err: 1, - ins: 1, - spa: 1, - sr: 0, - loaded: 1 - }) + body: JSON.stringify({ stn: 0, err: 1, ins: 1, spa: 1, sr: 0, loaded: 1 }) }) - await getUrlString.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(() => { - expect(stPayloadReceived).toBeUndefined() // trace payload should've been received by now after page has loaded - }) + let stPayloadReceived + browser.testHandle.expectResources().then(res => stPayloadReceived = res) + await browser.url(await getUrlString) + await browser.waitForAgentLoad() + expect(stPayloadReceived).toBeUndefined() // trace payload should've been received by now after page has loaded }) it('does run (standalone behavior) if stn flag is 1', async () => { // The default rum response will include stn = 1 and sr = 0. - await getUrlString.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(async () => { - const getTraceMode = browser.execute(function () { // expect Trace to be running by itself - return Object.values(newrelic.initializedAgents)[0].features.session_trace.featAggregate.isStandalone - }) - expect(stPayloadReceived).toBeTruthy() - await expect(getTraceMode).resolves.toBeTruthy() + let getFirstSTPayload = browser.testHandle.expectResources(3000) + await browser.url(await getUrlString) + await browser.waitForAgentLoad() + await expect(getFirstSTPayload).resolves.toBeTruthy() + const traceMode = await browser.execute(function () { // expect Trace to be running by itself + return Object.values(newrelic.initializedAgents)[0].features.session_trace.featAggregate.isStandalone }) + expect(traceMode).toBeTruthy() }) }) }) describe('Trace when replay entitlement is 1 and stn is 1', () => { - let initSTReceived beforeEach(async () => { await browser.destroyAgentSession() await browser.testHandle.scheduleReply('bamServer', { test: testRumRequest, permanent: true, // note this is set since the tests in this block also tests subsequent load behavior - body: JSON.stringify({ - stn: 1, - err: 1, - ins: 1, - spa: 1, - sr: 1, - loaded: 1 - }) + body: JSON.stringify({ stn: 1, err: 1, ins: 1, spa: 1, sr: 1, loaded: 1 }) }) - - initSTReceived = undefined - browser.testHandle.expectResources().then(resPayload => initSTReceived = resPayload) }) afterEach(async () => { await browser.testHandle.clearScheduledReplies('bamServer') }) + async function navigateToRootDir () { + await browser.url(await browser.testHandle.assetURL('/')) + try { // IE does not like this command, though the rest of the test below still works + await browser.waitUntil(() => browser.execute(function () { document.readyState === 'complete' }), { timeout: 5000 }) + } catch (e) {} + } it('still runs when replay feature is missing or disabled', async () => { const urlWithoutReplay = await browser.testHandle.assetURL('stn/instrumented.html', { init: { privacy: { cookies_enabled: true }, session_replay: { enabled: false } } }) @@ -90,26 +75,29 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { ] }) - let firstPageAgentVals, secondPageAgentVals - await browser.url(urlWithoutReplay).then(() => browser.waitForAgentLoad()) - firstPageAgentVals = await getTraceValues() + let getFirstSTPayload = browser.testHandle.expectResources(3000) + await browser.url(urlWithoutReplay) + await browser.waitForAgentLoad() + let initSTReceived = await getFirstSTPayload + let firstPageAgentVals = await getTraceValues() expect(initSTReceived).toBeTruthy() // that is, trace should still fully run when the replay feature isn't around expect(initSTReceived.request.query.ptid).toBeUndefined() // trace doesn't have ptid on first initial harvest expect(firstPageAgentVals).toEqual([true, MODE.FULL, expect.any(String)]) - await browser.url(await browser.testHandle.assetURL('/')) + await navigateToRootDir() - let anotherInitST = browser.testHandle.expectResources(3000) - await browser.url(urlWithoutReplay).then(() => browser.waitForAgentLoad()) - await anotherInitST.then(async (secondInitST) => { - secondPageAgentVals = await getTraceValues() - // On subsequent page load or refresh, trace should maintain the set mode, standalone, and same sessionid but have a new ptid corresponding to new page visit. - expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) - expect(secondInitST.request.query.ptid).toBeUndefined() - expect(secondPageAgentVals).toEqual([true, MODE.FULL, expect.any(String)]) // note it's expected & assumed that the replay mode is OFF + let nextResourceHarvest = browser.testHandle.expectResources(3000) + await browser.url(await browser.testHandle.assetURL('instrumented.html', { init: { privacy: { cookies_enabled: true }, session_replay: { enabled: false } } })) + await browser.waitForAgentLoad() // ^for some reason, macOS Safari (up to 16.1) would fail if we navigated back to 'urlWithoutReplay' so we go to a diff asset page instead - expect(secondPageAgentVals[2]).not.toEqual(firstPageAgentVals[2]) // ptids - }) + let secondInitST = await nextResourceHarvest + let secondPageAgentVals = await getTraceValues() + // On subsequent page load or refresh, trace should maintain the set mode, standalone, and same sessionid but have a new ptid corresponding to new page visit. + expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) + expect(secondInitST.request.query.ptid).toBeUndefined() + expect(secondPageAgentVals).toEqual([true, MODE.FULL, expect.any(String)]) // note it's expected & assumed that the replay mode is OFF + + expect(secondPageAgentVals[2]).not.toEqual(firstPageAgentVals[2]) // ptids }) ;[ @@ -128,31 +116,32 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { ] }) - let firstPageAgentVals, secondPageAgentVals - await browser.url(urlWithReplay).then(() => browser.waitForAgentLoad()) - firstPageAgentVals = await getRuntimeValues() + let getFirstSTPayload = browser.testHandle.expectResources(3000) + await browser.url(urlWithReplay) + await browser.waitForAgentLoad() + let initSTReceived = await getFirstSTPayload + let firstPageAgentVals = await getRuntimeValues() expect(initSTReceived).toBeTruthy() expect(initSTReceived.request.query.ptid).toBeUndefined() - if (replayMode === 'OFF') expect(firstPageAgentVals).toEqual([true, MODE.FULL, true]) else expect(firstPageAgentVals).toEqual([false, MODE.FULL, true]) // when replay is running, trace is no longer op in standalone mode - await browser.url(await browser.testHandle.assetURL('/')) - - let anotherInitST = browser.testHandle.expectResources(3000) - await browser.url(urlWithReplay).then(() => browser.waitForAgentLoad()) - await anotherInitST.then(async (secondInitST) => { - secondPageAgentVals = await getRuntimeValues() - // On subsequent page load or refresh, trace should maintain FULL mode and session id. - expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) - expect(secondInitST.request.query.ptid).toBeUndefined() // this validates we're actually getting the 2nd page's initial res, not 1st page's unload res - - if (replayMode === 'OFF') { - expect(secondPageAgentVals).toEqual([true, MODE.FULL, null]) // session_replay.featAggregate will be null as it's OFF and not imported on subsequent pages - } else { - expect(secondPageAgentVals).toEqual([false, MODE.FULL, true]) - } - }) + await navigateToRootDir() + + let nextResourceHarvest = browser.testHandle.expectResources(3000) + await browser.url(await browser.testHandle.assetURL('instrumented.html', config({ session_replay: replayConfig }))) + await browser.waitForAgentLoad() // ^for some reason, macOS Safari (up to 16.1) would fail if we navigated back to 'urlWithReplay' so we go to a diff asset page instead + + let secondInitST = await nextResourceHarvest + let secondPageAgentVals = await getRuntimeValues() + // On subsequent page load or refresh, trace should maintain FULL mode and session id. + expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) + expect(secondInitST.request.query.ptid).toBeUndefined() // this validates we're actually getting the 2nd page's initial res, not 1st page's unload res + if (replayMode === 'OFF') { + expect(secondPageAgentVals).toEqual([true, MODE.FULL, null]) // session_replay.featAggregate will be null as it's OFF and not imported on subsequent pages + } else { + expect(secondPageAgentVals).toEqual([false, MODE.FULL, true]) + } }) }) }) @@ -164,14 +153,7 @@ describe.withBrowsersMatching(notIE)('Trace when replay entitlement is 1 and stn await browser.testHandle.scheduleReply('bamServer', { test: testRumRequest, permanent: true, - body: JSON.stringify({ - stn: 0, - err: 1, - ins: 1, - spa: 1, - sr: 1, - loaded: 1 - }) + body: JSON.stringify({ stn: 0, err: 1, ins: 1, spa: 1, sr: 1, loaded: 1 }) }) initSTReceived = undefined @@ -179,10 +161,9 @@ describe.withBrowsersMatching(notIE)('Trace when replay entitlement is 1 and stn }) it('does not run when replay is OFF', async () => { - const urlReplayOff = await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: { sampleRate: 0, errorSampleRate: 0 } })) - await browser.url(urlReplayOff).then(() => browser.waitForAgentLoad()).then(() => { - expect(initSTReceived).toBeUndefined() - }) + await browser.url(await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: { sampleRate: 0, errorSampleRate: 0 } }))) + await browser.waitForAgentLoad() + expect(initSTReceived).toBeUndefined() }) ;[ @@ -199,24 +180,23 @@ describe.withBrowsersMatching(notIE)('Trace when replay entitlement is 1 and stn ] }) - await browser.url(urlReplayOn).then(() => browser.waitForAgentLoad()).then(async () => { - if (replayMode === 'FULL') { - await expect(getAssumedValues()).resolves.toEqual([false, MODE.FULL]) - expect(initSTReceived).toBeTruthy() - - // When not in standalone, trace bypasses the old rate limiting of only harvesting on 30+ nodes. In practice, we should get few-secs-span harvests without that threshold. - const second = await browser.testHandle.expectResources(3000).then(payload => payload.request.body.res.length) // 2nd harvest is usually riddled with a bunch of startup resource nodes - const third = await browser.testHandle.expectResources(3000).then(payload => payload.request.body.res.length) - expect([second, third].some(length => length < 30)).toBeTruthy() - } else if (replayMode === 'ERR') { - await expect(getAssumedValues()).resolves.toEqual([false, MODE.ERROR]) - expect(initSTReceived).toBeUndefined() // trace in error mode is not expected to send anything on startup - } - - return browser.refresh().then(() => browser.waitForAgentLoad()) - }).then(async () => { - await expect(getAssumedValues()).resolves.toEqual([false, replayMode === 'FULL' ? MODE.FULL : MODE.ERROR]) // page loads of existing session should use same trace mode even if stn = 0 - }) + await browser.url(urlReplayOn) + await browser.waitForAgentLoad() + if (replayMode === 'FULL') { + await expect(getAssumedValues()).resolves.toEqual([false, MODE.FULL]) + expect(initSTReceived).toBeTruthy() + + // When not in standalone, trace bypasses the old rate limiting of only harvesting on 30+ nodes. In practice, we should get few-secs-span harvests without that threshold. + const second = await browser.testHandle.expectResources(3000).then(payload => payload.request.body.res.length) // 2nd harvest is usually riddled with a bunch of startup resource nodes + const third = await browser.testHandle.expectResources(3000).then(payload => payload.request.body.res.length) + expect([second, third].some(length => length < 30)).toBeTruthy() + } else if (replayMode === 'ERR') { + await expect(getAssumedValues()).resolves.toEqual([false, MODE.ERROR]) + expect(initSTReceived).toBeUndefined() // trace in error mode is not expected to send anything on startup + } + + await browser.refresh().then(() => browser.waitForAgentLoad()) + await expect(getAssumedValues()).resolves.toEqual([false, replayMode === 'FULL' ? MODE.FULL : MODE.ERROR]) // page loads of existing session should use same trace mode even if stn = 0 }) }) }) From 09feba69cd6033e9aadd189663c987fafb8d9a9f Mon Sep 17 00:00:00 2001 From: Chunwai Li Date: Fri, 30 Jun 2023 14:44:34 -0500 Subject: [PATCH 11/14] mostly good with no-retry on all xcept saf 15 --- tests/specs/stn/with-replay-error-mode.e2e.js | 59 ++++++++++--------- tests/specs/stn/with-session-replay.e2e.js | 33 ++++------- 2 files changed, 44 insertions(+), 48 deletions(-) diff --git a/tests/specs/stn/with-replay-error-mode.e2e.js b/tests/specs/stn/with-replay-error-mode.e2e.js index 041f27650..d8e5fc66c 100644 --- a/tests/specs/stn/with-replay-error-mode.e2e.js +++ b/tests/specs/stn/with-replay-error-mode.e2e.js @@ -15,7 +15,7 @@ const getTraceMode = () => browser.execute(function () { }) describe.withBrowsersMatching(notIE)('Trace error mode', () => { - let initSTReceived, getReplayOnErrorUrl + let initSTReceived, getFirstSTPayload, getReplayOnErrorUrl beforeEach(async () => { await browser.destroyAgentSession() await browser.testHandle.scheduleReply('bamServer', { @@ -25,7 +25,7 @@ describe.withBrowsersMatching(notIE)('Trace error mode', () => { }) initSTReceived = undefined - browser.testHandle.expectResources().then(resPayload => initSTReceived = resPayload) + getFirstSTPayload = browser.testHandle.expectResources().then(resPayload => initSTReceived = resPayload) getReplayOnErrorUrl = browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: { sampleRate: 0, errorSampleRate: 1 }, session_trace: { harvestTimeSeconds: 2 } })) }) @@ -36,29 +36,34 @@ describe.withBrowsersMatching(notIE)('Trace error mode', () => { } it('switches to full mode when an error happens after page load', async () => { - await getReplayOnErrorUrl.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(async () => { - let startingMode = (await getTraceMode())[0] - expect(initSTReceived).toBeUndefined() - expect(startingMode).toEqual(MODE.ERROR) - - await browser.execute(simulateErrorInBrowser) - let modeAfterErr = (await getTraceMode())[0] - expect(modeAfterErr).toEqual(MODE.FULL) - // The loadEventEnd is part of PT and PNT resource entry and is a node created at start of page life. - expect(initSTReceived.request.body.res.find(node => node.n === 'loadEventEnd')).toEqual(expect.objectContaining({ n: 'loadEventEnd', o: 'document', t: 'timing' })) - - await expect(browser.testHandle.expectResources(3000)).resolves.toBeTruthy() // double check there's nothing wrong with full mdoe interval harvest - }) + await browser.url(await getReplayOnErrorUrl) + await browser.waitForAgentLoad() + await browser.waitUntil(() => browser.execute(async function () { // here for no-retry wdio stability + return await Object.values(newrelic.initializedAgents)[0].features.session_trace.onAggregateImported + }), { timeout: 5000 }) + let startingMode = (await getTraceMode())[0] + expect(initSTReceived).toBeUndefined() + expect(startingMode).toEqual(MODE.ERROR) + + await browser.execute(simulateErrorInBrowser) + await getFirstSTPayload + let modeAfterErr = (await getTraceMode())[0] + expect(modeAfterErr).toEqual(MODE.FULL) + // The loadEventEnd is part of PT and PNT resource entry and is a node created at start of page life. + expect(initSTReceived.request.body.res.find(node => node.n === 'loadEventEnd')).toEqual(expect.objectContaining({ n: 'loadEventEnd', o: 'document', t: 'timing' })) + + await expect(browser.testHandle.expectResources(3000)).resolves.toBeTruthy() // double check there's nothing wrong with full mdoe interval harvest }) it('starts in full mode when an error happens before page load', async () => { - await getReplayOnErrorUrl.then(builtUrl => browser.url(builtUrl)).then(() => browser.execute(simulateErrorInBrowser)).then(() => browser.waitForAgentLoad()).then(async () => { - let startingMode = (await getTraceMode())[0] - expect(initSTReceived).toBeTruthy() - expect(startingMode).toEqual(MODE.FULL) - - await expect(browser.testHandle.expectResources(3000)).resolves.toBeTruthy() - }) + await browser.url(await getReplayOnErrorUrl) + await browser.execute(simulateErrorInBrowser) + await browser.waitForAgentLoad() + await expect(getFirstSTPayload).resolves.toBeTruthy() + let startingMode = (await getTraceMode())[0] + expect(startingMode).toEqual(MODE.FULL) + + await expect(browser.testHandle.expectResources(3000)).resolves.toBeTruthy() }) it.withBrowsersMatching(onlyChrome)('does not capture more than the last 30 seconds when error happens', async () => { @@ -105,7 +110,6 @@ describe.withBrowsersMatching(notIE)('Trace error mode', () => { }) describe.withBrowsersMatching(notIE)('Trace when replay runs then is aborted', () => { - let initSTReceived beforeEach(async () => { await browser.destroyAgentSession() await browser.testHandle.scheduleReply('bamServer', { @@ -113,9 +117,6 @@ describe.withBrowsersMatching(notIE)('Trace when replay runs then is aborted', ( permanent: true, body: JSON.stringify({ stn: 0, err: 1, ins: 1, spa: 1, sr: 1, loaded: 1 }) }) - - initSTReceived = undefined - browser.testHandle.expectResources().then(resPayload => initSTReceived = resPayload) }) const triggerReplayAbort = () => browser.execute(function () { Object.values(NREUM.initializedAgents)[0].runtime.session.reset() }) @@ -126,11 +127,13 @@ describe.withBrowsersMatching(notIE)('Trace when replay runs then is aborted', ( ].forEach(([description, supposedMode, replayConfig]) => { it(description, async () => { let url = await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: replayConfig, session_trace: { harvestTimeSeconds: 2 } })) + let getFirstSTPayload = browser.testHandle.expectResources(3000) await browser.url(url) await browser.waitForAgentLoad() + + if (supposedMode === MODE.FULL) await expect(getFirstSTPayload).resolves.toBeTruthy() + else await expect(getFirstSTPayload).rejects.toThrow() // in ERROR mode expect(await getTraceMode()).toEqual([supposedMode, false]) - if (supposedMode === MODE.FULL) expect(initSTReceived).toBeTruthy() - else expect(initSTReceived).toBeUndefined() // in ERROR mode let lastSTHarvest = browser.testHandle.expectResources(1000) // abort should cause a harvest right away (in FULL), rather than the usual interval await triggerReplayAbort() diff --git a/tests/specs/stn/with-session-replay.e2e.js b/tests/specs/stn/with-session-replay.e2e.js index f6411eaf8..502330012 100644 --- a/tests/specs/stn/with-session-replay.e2e.js +++ b/tests/specs/stn/with-session-replay.e2e.js @@ -63,9 +63,15 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { await browser.waitUntil(() => browser.execute(function () { document.readyState === 'complete' }), { timeout: 5000 }) } catch (e) {} } + async function loadPageAndGetResource (assetUrlArgs, timeout) { + const url = await browser.testHandle.assetURL(...assetUrlArgs) + const getSTPayload = browser.testHandle.expectResources(timeout) + await browser.url(url) + await browser.waitForAgentLoad() + return await getSTPayload + } it('still runs when replay feature is missing or disabled', async () => { - const urlWithoutReplay = await browser.testHandle.assetURL('stn/instrumented.html', { init: { privacy: { cookies_enabled: true }, session_replay: { enabled: false } } }) const getTraceValues = () => browser.execute(function () { const agent = Object.values(newrelic.initializedAgents)[0] return [ @@ -75,10 +81,7 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { ] }) - let getFirstSTPayload = browser.testHandle.expectResources(3000) - await browser.url(urlWithoutReplay) - await browser.waitForAgentLoad() - let initSTReceived = await getFirstSTPayload + let initSTReceived = await loadPageAndGetResource(['stn/instrumented.html', { init: { privacy: { cookies_enabled: true }, session_replay: { enabled: false } } }], 3001) let firstPageAgentVals = await getTraceValues() expect(initSTReceived).toBeTruthy() // that is, trace should still fully run when the replay feature isn't around expect(initSTReceived.request.query.ptid).toBeUndefined() // trace doesn't have ptid on first initial harvest @@ -86,11 +89,8 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { await navigateToRootDir() - let nextResourceHarvest = browser.testHandle.expectResources(3000) - await browser.url(await browser.testHandle.assetURL('instrumented.html', { init: { privacy: { cookies_enabled: true }, session_replay: { enabled: false } } })) - await browser.waitForAgentLoad() // ^for some reason, macOS Safari (up to 16.1) would fail if we navigated back to 'urlWithoutReplay' so we go to a diff asset page instead - - let secondInitST = await nextResourceHarvest + // For some reason, macOS Safari (up to 16.1) would fail if we navigated back to 'urlWithoutReplay' so we go to a diff asset page instead: + let secondInitST = await loadPageAndGetResource(['instrumented.html', { init: { privacy: { cookies_enabled: true }, session_replay: { enabled: false } } }], 3002) let secondPageAgentVals = await getTraceValues() // On subsequent page load or refresh, trace should maintain the set mode, standalone, and same sessionid but have a new ptid corresponding to new page visit. expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) @@ -106,7 +106,6 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { ['ERR', { sampleRate: 0, errorSampleRate: 1 }] ].forEach(([replayMode, replayConfig]) => { it.withBrowsersMatching(notIE)(`runs in full when replay feature is present and in ${replayMode} mode`, async () => { - const urlWithReplay = await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: replayConfig })) const getRuntimeValues = () => browser.execute(function () { const agent = Object.values(newrelic.initializedAgents)[0] return [ @@ -116,10 +115,7 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { ] }) - let getFirstSTPayload = browser.testHandle.expectResources(3000) - await browser.url(urlWithReplay) - await browser.waitForAgentLoad() - let initSTReceived = await getFirstSTPayload + let initSTReceived = await loadPageAndGetResource(['stn/instrumented.html', config({ session_replay: replayConfig })], 3003) let firstPageAgentVals = await getRuntimeValues() expect(initSTReceived).toBeTruthy() expect(initSTReceived.request.query.ptid).toBeUndefined() @@ -128,11 +124,8 @@ describe('Trace when replay entitlement is 1 and stn is 1', () => { await navigateToRootDir() - let nextResourceHarvest = browser.testHandle.expectResources(3000) - await browser.url(await browser.testHandle.assetURL('instrumented.html', config({ session_replay: replayConfig }))) - await browser.waitForAgentLoad() // ^for some reason, macOS Safari (up to 16.1) would fail if we navigated back to 'urlWithReplay' so we go to a diff asset page instead - - let secondInitST = await nextResourceHarvest + // For some reason, macOS Safari (up to 16.1) would fail if we navigated back to 'urlWithoutReplay' so we go to a diff asset page instead: + let secondInitST = await loadPageAndGetResource(['instrumented.html', config({ session_replay: replayConfig })], 3004) let secondPageAgentVals = await getRuntimeValues() // On subsequent page load or refresh, trace should maintain FULL mode and session id. expect(secondInitST.request.query.s).toEqual(initSTReceived.request.query.s) From 97a3d65a09226e9267be3a38cb3a467043979e08 Mon Sep 17 00:00:00 2001 From: Chunwai Li Date: Tue, 18 Jul 2023 12:50:23 -0500 Subject: [PATCH 12/14] Replace initstreceivved --- tests/specs/stn/with-replay-error-mode.e2e.js | 58 +++++++++---------- tools/testing-server/test-handle.js | 1 + 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/tests/specs/stn/with-replay-error-mode.e2e.js b/tests/specs/stn/with-replay-error-mode.e2e.js index d8e5fc66c..945dc310f 100644 --- a/tests/specs/stn/with-replay-error-mode.e2e.js +++ b/tests/specs/stn/with-replay-error-mode.e2e.js @@ -15,7 +15,7 @@ const getTraceMode = () => browser.execute(function () { }) describe.withBrowsersMatching(notIE)('Trace error mode', () => { - let initSTReceived, getFirstSTPayload, getReplayOnErrorUrl + let getReplayOnErrorUrl beforeEach(async () => { await browser.destroyAgentSession() await browser.testHandle.scheduleReply('bamServer', { @@ -24,9 +24,7 @@ describe.withBrowsersMatching(notIE)('Trace error mode', () => { body: JSON.stringify({ stn: 0, err: 1, ins: 1, spa: 1, sr: 1, loaded: 1 }) }) - initSTReceived = undefined - getFirstSTPayload = browser.testHandle.expectResources().then(resPayload => initSTReceived = resPayload) - getReplayOnErrorUrl = browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: { sampleRate: 0, errorSampleRate: 1 }, session_trace: { harvestTimeSeconds: 2 } })) + getReplayOnErrorUrl = browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: { sampleRate: 0, errorSampleRate: 1 }, session_trace: { harvestTimeSeconds: 3 } })) }) function simulateErrorInBrowser () { // this is a way to throw error in WdIO / Selenium without killing the test itself @@ -36,41 +34,40 @@ describe.withBrowsersMatching(notIE)('Trace error mode', () => { } it('switches to full mode when an error happens after page load', async () => { - await browser.url(await getReplayOnErrorUrl) - await browser.waitForAgentLoad() - await browser.waitUntil(() => browser.execute(async function () { // here for no-retry wdio stability - return await Object.values(newrelic.initializedAgents)[0].features.session_trace.onAggregateImported - }), { timeout: 5000 }) - let startingMode = (await getTraceMode())[0] - expect(initSTReceived).toBeUndefined() - expect(startingMode).toEqual(MODE.ERROR) - - await browser.execute(simulateErrorInBrowser) - await getFirstSTPayload - let modeAfterErr = (await getTraceMode())[0] - expect(modeAfterErr).toEqual(MODE.FULL) + await Promise.all([ + browser.testHandle.expectResources(5001, true), + browser.url(await getReplayOnErrorUrl).then(() => browser.waitForFeatureAggregate('session_trace')) + ]) + await expect(getTraceMode()).resolves.toEqual([MODE.ERROR, false]) + + const [resources] = await Promise.all([ + browser.testHandle.expectResources(), + browser.execute(simulateErrorInBrowser) + ]) + await expect(getTraceMode()).resolves.toEqual([MODE.FULL, false]) // The loadEventEnd is part of PT and PNT resource entry and is a node created at start of page life. - expect(initSTReceived.request.body.res.find(node => node.n === 'loadEventEnd')).toEqual(expect.objectContaining({ n: 'loadEventEnd', o: 'document', t: 'timing' })) + expect(resources.request.body.res).toEqual(expect.arrayContaining([expect.objectContaining({ + n: 'loadEventEnd', o: 'document', t: 'timing' + })])) - await expect(browser.testHandle.expectResources(3000)).resolves.toBeTruthy() // double check there's nothing wrong with full mdoe interval harvest + await expect(browser.testHandle.expectResources(5002)).resolves.toBeTruthy() // double check there's nothing wrong with full mode interval harvest }) it('starts in full mode when an error happens before page load', async () => { - await browser.url(await getReplayOnErrorUrl) - await browser.execute(simulateErrorInBrowser) + let getFirstSTPayload = browser.testHandle.expectResources(5010) + await browser.url(await browser.testHandle.assetURL('js-error-with-error-before-page-load.html', config({ session_replay: { sampleRate: 0, errorSampleRate: 1 }, session_trace: { harvestTimeSeconds: 3 } }))) await browser.waitForAgentLoad() await expect(getFirstSTPayload).resolves.toBeTruthy() - let startingMode = (await getTraceMode())[0] - expect(startingMode).toEqual(MODE.FULL) + await expect(getTraceMode()).resolves.toEqual([MODE.FULL, false]) - await expect(browser.testHandle.expectResources(3000)).resolves.toBeTruthy() + await expect(browser.testHandle.expectResources(5011)).resolves.toBeTruthy() }) it.withBrowsersMatching(onlyChrome)('does not capture more than the last 30 seconds when error happens', async () => { await getReplayOnErrorUrl.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(async () => { await browser.pause(30000) await Promise.all([ - browser.testHandle.expectResources(3000), // the setup expectRes promise would've timed out already -- you will see some console errors but they don't impact validity + browser.testHandle.expectResources(5020), // the setup expectRes promise would've timed out already -- you will see some console errors but they don't impact validity browser.execute(simulateErrorInBrowser) ]).then(async ([initSTAfterErr]) => { expect(initSTAfterErr.request.body.res.find(node => node.n === 'loadEventEnd')).toBeUndefined() // that node should've been tossed out by now @@ -79,12 +76,15 @@ describe.withBrowsersMatching(notIE)('Trace error mode', () => { }) it('does not perform final harvest while in this mode', async () => { + let resources = browser.testHandle.expectResources(5030, true) await getReplayOnErrorUrl.then(builtUrl => browser.url(builtUrl)).then(() => browser.waitForAgentLoad()).then(async () => { - await getTraceMode().then(traceState => expect(traceState).toEqual([MODE.ERROR, false])) // sanity check tbh - expect(initSTReceived).toBeUndefined() + await expect(getTraceMode()).resolves.toEqual([MODE.ERROR, false]) // sanity check tbh + await expect(resources).resolves.toBeUndefined() + + resources = browser.testHandle.expectResources(5031, true) }).then(() => browser.refresh()).then(() => browser.waitForAgentLoad()).then(async () => { - await getTraceMode().then(traceState => expect(traceState).toEqual([MODE.ERROR, false])) - expect(initSTReceived).toBeUndefined() // no harvest from either previous unload or from new existing-session load + await expect(getTraceMode()).resolves.toEqual([MODE.ERROR, false]) + await expect(resources).resolves.toBeUndefined() // no harvest from either previous unload or from new existing-session load }) }) diff --git a/tools/testing-server/test-handle.js b/tools/testing-server/test-handle.js index 06bef4077..613806a04 100644 --- a/tools/testing-server/test-handle.js +++ b/tools/testing-server/test-handle.js @@ -220,6 +220,7 @@ module.exports = class TestHandle { `Expect ${testName} for ${serverId} timed out after ${testServerExpect.timeout || this.#testServer.config.timeout}ms for test ${this.#testId}` )) } + this.#pendingExpects.get(serverId).delete(deferred) }, testServerExpect.timeout || this.#testServer.config.timeout) } From 982b56de1cd6915fede9fc6dfd080704daa0b44a Mon Sep 17 00:00:00 2001 From: Chunwai Li Date: Wed, 19 Jul 2023 09:50:04 -0500 Subject: [PATCH 13/14] pr comment --- package-lock.json | 12 ++++++------ tests/specs/stn/with-session-replay.e2e.js | 5 ++--- tools/browsers-lists/browsers-supported.json | 8 ++++---- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7affce05e..fcc78bd5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9687,9 +9687,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001516", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001516.tgz", - "integrity": "sha512-Wmec9pCBY8CWbmI4HsjBeQLqDTqV91nFVR83DnZpYyRnPI1wePDsTg0bGLPC5VU/3OIZV1fmxEea1b+tFKe86g==", + "version": "1.0.30001517", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz", + "integrity": "sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==", "dev": true, "funding": [ { @@ -34042,9 +34042,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001516", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001516.tgz", - "integrity": "sha512-Wmec9pCBY8CWbmI4HsjBeQLqDTqV91nFVR83DnZpYyRnPI1wePDsTg0bGLPC5VU/3OIZV1fmxEea1b+tFKe86g==", + "version": "1.0.30001517", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz", + "integrity": "sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==", "dev": true }, "capital-case": { diff --git a/tests/specs/stn/with-session-replay.e2e.js b/tests/specs/stn/with-session-replay.e2e.js index 502330012..5370432d5 100644 --- a/tests/specs/stn/with-session-replay.e2e.js +++ b/tests/specs/stn/with-session-replay.e2e.js @@ -23,11 +23,10 @@ import { notIE } from '../../../tools/browser-matcher/common-matchers.mjs' body: JSON.stringify({ stn: 0, err: 1, ins: 1, spa: 1, sr: 0, loaded: 1 }) }) - let stPayloadReceived - browser.testHandle.expectResources().then(res => stPayloadReceived = res) + let resources = browser.testHandle.expectResources(10000) await browser.url(await getUrlString) await browser.waitForAgentLoad() - expect(stPayloadReceived).toBeUndefined() // trace payload should've been received by now after page has loaded + await expect(resources).resolves.toBeUndefined() // trace payload should've been received by now after page has loaded }) it('does run (standalone behavior) if stn flag is 1', async () => { diff --git a/tools/browsers-lists/browsers-supported.json b/tools/browsers-lists/browsers-supported.json index 9333e36f5..b1d5dd579 100644 --- a/tools/browsers-lists/browsers-supported.json +++ b/tools/browsers-lists/browsers-supported.json @@ -4,15 +4,15 @@ "browserName": "chrome", "platformName": "Windows 11", "platform": "Windows 11", - "version": "105", - "browserVersion": "105" + "version": "106", + "browserVersion": "106" }, { "browserName": "chrome", "platformName": "Windows 11", "platform": "Windows 11", - "version": "108", - "browserVersion": "108" + "version": "109", + "browserVersion": "109" }, { "browserName": "chrome", From f764b13ca8b236082c955a8d92608fd66c13b92c Mon Sep 17 00:00:00 2001 From: Jordan Porter Date: Thu, 27 Jul 2023 13:09:26 -0600 Subject: [PATCH 14/14] fix tests --- tests/specs/stn/with-session-replay.e2e.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/specs/stn/with-session-replay.e2e.js b/tests/specs/stn/with-session-replay.e2e.js index 5370432d5..2e00698c3 100644 --- a/tests/specs/stn/with-session-replay.e2e.js +++ b/tests/specs/stn/with-session-replay.e2e.js @@ -23,19 +23,19 @@ import { notIE } from '../../../tools/browser-matcher/common-matchers.mjs' body: JSON.stringify({ stn: 0, err: 1, ins: 1, spa: 1, sr: 0, loaded: 1 }) }) - let resources = browser.testHandle.expectResources(10000) - await browser.url(await getUrlString) - await browser.waitForAgentLoad() - await expect(resources).resolves.toBeUndefined() // trace payload should've been received by now after page has loaded + await Promise.all([ + browser.url(await getUrlString).then(() => browser.waitForAgentLoad()), + browser.testHandle.expectResources(10000, true) + ]) }) it('does run (standalone behavior) if stn flag is 1', async () => { // The default rum response will include stn = 1 and sr = 0. + await Promise.all([ + browser.url(await getUrlString).then(() => browser.waitForAgentLoad()), + browser.testHandle.expectResources() + ]) - let getFirstSTPayload = browser.testHandle.expectResources(3000) - await browser.url(await getUrlString) - await browser.waitForAgentLoad() - await expect(getFirstSTPayload).resolves.toBeTruthy() const traceMode = await browser.execute(function () { // expect Trace to be running by itself return Object.values(newrelic.initializedAgents)[0].features.session_trace.featAggregate.isStandalone })