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/src/features/session_trace/aggregate/index.js b/src/features/session_trace/aggregate/index.js index 90d2cdb89..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 } @@ -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 12c003695..9bdbf9582 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 ce611da30..4fd7d5462 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-replay-error-mode.e2e.js b/tests/specs/stn/with-replay-error-mode.e2e.js new file mode 100644 index 000000000..945dc310f --- /dev/null +++ b/tests/specs/stn/with-replay-error-mode.e2e.js @@ -0,0 +1,152 @@ +/* + * 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' + +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 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 }) + }) + + 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 + 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 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(resources.request.body.res).toEqual(expect.arrayContaining([expect.objectContaining({ + n: 'loadEventEnd', o: 'document', t: 'timing' + })])) + + 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 () => { + 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() + await expect(getTraceMode()).resolves.toEqual([MODE.FULL, false]) + + 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(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 + }) + }) + }) + + 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 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 expect(getTraceMode()).resolves.toEqual([MODE.ERROR, false]) + await expect(resources).resolves.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 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. + + 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', () => { + 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 }) + }) + }) + + 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 } })) + 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]) + + 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 new file mode 100644 index 000000000..2e00698c3 --- /dev/null +++ b/tests/specs/stn/with-session-replay.e2e.js @@ -0,0 +1,194 @@ +/* + * 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' + +[ + ['session tracking is disabled', false], + ['session tracking enabled but replay entitlement is 0', true] +].forEach(([run, trackingOn]) => { + describe(`Trace behavior when ${run}`, () => { + let getUrlString + beforeEach(() => { + 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 }) + }) + + 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() + ]) + + 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', () => { + 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 }) + }) + }) + 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) {} + } + 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 getTraceValues = () => browser.execute(function () { + const agent = Object.values(newrelic.initializedAgents)[0] + return [ + agent.features.session_trace.featAggregate.isStandalone, + agent.runtime.session.state.sessionTraceMode, + agent.runtime.ptid + ] + }) + + 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 + expect(firstPageAgentVals).toEqual([true, MODE.FULL, expect.any(String)]) + + await navigateToRootDir() + + // 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) + 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 + }) + + ;[ + ['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 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 + ] + }) + + 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() + 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 navigateToRootDir() + + // 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) + 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]) + } + }) + }) +}) + +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({ stn: 0, err: 1, ins: 1, spa: 1, sr: 1, loaded: 1 }) + }) + + initSTReceived = undefined + browser.testHandle.expectResources().then(resPayload => initSTReceived = resPayload) + }) + + it('does not run when replay is OFF', async () => { + await browser.url(await browser.testHandle.assetURL('stn/instrumented.html', config({ session_replay: { sampleRate: 0, errorSampleRate: 0 } }))) + await browser.waitForAgentLoad() + expect(initSTReceived).toBeUndefined() + }) + + ;[ + ['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, 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) + 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 + }) + }) +}) 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') 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", diff --git a/tools/testing-server/routes/bam-apis.js b/tools/testing-server/routes/bam-apis.js index 5b9613077..f5835ab01 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()) } }) }) 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) }