replayReader.spec.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import {EventType} from '@sentry-internal/rrweb';
  2. import {BreadcrumbType} from 'sentry/types/breadcrumbs';
  3. import ReplayReader from 'sentry/utils/replays/replayReader';
  4. describe('ReplayReader', () => {
  5. const replayRecord = TestStubs.ReplayRecord({});
  6. it('Should return null if there are missing arguments', () => {
  7. const missingAttachments = ReplayReader.factory({
  8. attachments: undefined,
  9. errors: [],
  10. replayRecord,
  11. });
  12. expect(missingAttachments).toBeNull();
  13. const missingErrors = ReplayReader.factory({
  14. attachments: [],
  15. errors: undefined,
  16. replayRecord,
  17. });
  18. expect(missingErrors).toBeNull();
  19. const missingRecord = ReplayReader.factory({
  20. attachments: [],
  21. errors: [],
  22. replayRecord: undefined,
  23. });
  24. expect(missingRecord).toBeNull();
  25. });
  26. it('should calculate started_at/finished_at/duration based on first/last events', () => {
  27. const minuteZero = new Date('2023-12-25T00:00:00');
  28. const minuteTen = new Date('2023-12-25T00:10:00');
  29. const replay = ReplayReader.factory({
  30. attachments: [
  31. TestStubs.Replay.ConsoleEvent({timestamp: minuteZero}),
  32. TestStubs.Replay.ConsoleEvent({timestamp: minuteTen}),
  33. ],
  34. errors: [],
  35. replayRecord: TestStubs.ReplayRecord({
  36. started_at: new Date('2023-12-25T00:01:00'),
  37. finished_at: new Date('2023-12-25T00:09:00'),
  38. duration: undefined, // will be calculated
  39. }),
  40. });
  41. const expectedDuration = 10 * 60 * 1000; // 10 minutes, in ms
  42. expect(replay?.getReplay().started_at).toEqual(minuteZero);
  43. expect(replay?.getReplay().finished_at).toEqual(minuteTen);
  44. expect(replay?.getReplay().duration.asMilliseconds()).toEqual(expectedDuration);
  45. expect(replay?.getDurationMs()).toEqual(expectedDuration);
  46. });
  47. it('should make the replayRecord available through a getter method', () => {
  48. const replay = ReplayReader.factory({
  49. attachments: [],
  50. errors: [],
  51. replayRecord,
  52. });
  53. expect(replay?.getReplay()).toEqual(replayRecord);
  54. });
  55. describe('attachment splitting', () => {
  56. const timestamp = new Date('2023-12-25T00:02:00');
  57. const secondTimestamp = new Date('2023-12-25T00:04:00');
  58. const thirdTimestamp = new Date('2023-12-25T00:05:00');
  59. const optionsFrame = TestStubs.Replay.OptionFrame({});
  60. const optionsEvent = TestStubs.Replay.OptionFrameEvent({
  61. timestamp,
  62. data: {payload: optionsFrame},
  63. });
  64. const firstDiv = TestStubs.Replay.RRWebFullSnapshotFrameEvent({timestamp});
  65. const secondDiv = TestStubs.Replay.RRWebFullSnapshotFrameEvent({timestamp});
  66. const clickEvent = TestStubs.Replay.ClickEvent({timestamp});
  67. const secondClickEvent = TestStubs.Replay.ClickEvent({timestamp: secondTimestamp});
  68. const thirdClickEvent = TestStubs.Replay.ClickEvent({timestamp: thirdTimestamp});
  69. const deadClickEvent = TestStubs.Replay.DeadClickEvent({timestamp});
  70. const firstMemory = TestStubs.Replay.MemoryEvent({
  71. startTimestamp: timestamp,
  72. endTimestamp: timestamp,
  73. });
  74. const secondMemory = TestStubs.Replay.MemoryEvent({
  75. startTimestamp: timestamp,
  76. endTimestamp: timestamp,
  77. });
  78. const navigationEvent = TestStubs.Replay.NavigateEvent({
  79. startTimestamp: new Date('2023-12-25T00:03:00'),
  80. endTimestamp: new Date('2023-12-25T00:03:30'),
  81. });
  82. const navCrumb = TestStubs.Replay.BreadcrumbFrameEvent({
  83. timestamp: new Date('2023-12-25T00:03:00'),
  84. data: {
  85. payload: TestStubs.Replay.NavFrame({
  86. timestamp: new Date('2023-12-25T00:03:00'),
  87. }),
  88. },
  89. });
  90. const consoleEvent = TestStubs.Replay.ConsoleEvent({timestamp});
  91. const customEvent = TestStubs.Replay.BreadcrumbFrameEvent({
  92. timestamp: new Date('2023-12-25T00:02:30'),
  93. data: {
  94. payload: {
  95. category: 'redux.action',
  96. data: {
  97. action: 'save.click',
  98. },
  99. message: '',
  100. timestamp: new Date('2023-12-25T00:02:30').getTime() / 1000,
  101. type: BreadcrumbType.DEFAULT,
  102. },
  103. },
  104. });
  105. const attachments = [
  106. clickEvent,
  107. secondClickEvent,
  108. thirdClickEvent,
  109. consoleEvent,
  110. firstDiv,
  111. firstMemory,
  112. navigationEvent,
  113. navCrumb,
  114. optionsEvent,
  115. secondDiv,
  116. secondMemory,
  117. customEvent,
  118. deadClickEvent,
  119. ];
  120. it.each([
  121. {
  122. method: 'getRRWebFrames',
  123. expected: [
  124. {
  125. type: EventType.Custom,
  126. timestamp: expect.any(Number),
  127. data: {tag: 'replay.start', payload: {}},
  128. },
  129. firstDiv,
  130. secondDiv,
  131. {
  132. type: EventType.Custom,
  133. timestamp: expect.any(Number),
  134. data: {tag: 'replay.end', payload: {}},
  135. },
  136. ],
  137. },
  138. {
  139. method: 'getConsoleFrames',
  140. expected: [
  141. expect.objectContaining({category: 'console'}),
  142. expect.objectContaining({category: 'redux.action'}),
  143. ],
  144. },
  145. {
  146. method: 'getNetworkFrames',
  147. expected: [expect.objectContaining({op: 'navigation.navigate'})],
  148. },
  149. {
  150. method: 'getDOMFrames',
  151. expected: [
  152. expect.objectContaining({category: 'ui.slowClickDetected'}),
  153. expect.objectContaining({category: 'ui.click'}),
  154. expect.objectContaining({category: 'ui.click'}),
  155. ],
  156. },
  157. {
  158. method: 'getMemoryFrames',
  159. expected: [
  160. expect.objectContaining({op: 'memory'}),
  161. expect.objectContaining({op: 'memory'}),
  162. ],
  163. },
  164. {
  165. method: 'getChapterFrames',
  166. expected: [
  167. expect.objectContaining({category: 'replay.init'}),
  168. expect.objectContaining({category: 'ui.slowClickDetected'}),
  169. expect.objectContaining({category: 'navigation'}),
  170. expect.objectContaining({op: 'navigation.navigate'}),
  171. expect.objectContaining({category: 'ui.click'}),
  172. expect.objectContaining({category: 'ui.click'}),
  173. ],
  174. },
  175. {
  176. method: 'getSDKOptions',
  177. expected: optionsFrame,
  178. },
  179. ])('Calling $method will filter frames', ({method, expected}) => {
  180. const replay = ReplayReader.factory({
  181. attachments,
  182. errors: [],
  183. replayRecord,
  184. });
  185. const exec = replay?.[method];
  186. expect(exec()).toStrictEqual(expected);
  187. });
  188. });
  189. it('shoud return the SDK config if there is a RecordingOptions event found', () => {
  190. const timestamp = new Date();
  191. const optionsFrame = TestStubs.Replay.OptionFrame({});
  192. const replay = ReplayReader.factory({
  193. attachments: [
  194. TestStubs.Replay.OptionFrameEvent({
  195. timestamp,
  196. data: {payload: optionsFrame},
  197. }),
  198. ],
  199. errors: [],
  200. replayRecord,
  201. });
  202. expect(replay?.getSDKOptions()).toBe(optionsFrame);
  203. });
  204. describe('isNetworkDetailsSetup', () => {
  205. it('should have isNetworkDetailsSetup=true if sdkConfig says so', () => {
  206. const timestamp = new Date();
  207. const replay = ReplayReader.factory({
  208. attachments: [
  209. TestStubs.Replay.OptionFrameEvent({
  210. timestamp,
  211. data: {
  212. payload: TestStubs.Replay.OptionFrame({
  213. networkDetailHasUrls: true,
  214. }),
  215. },
  216. }),
  217. ],
  218. errors: [],
  219. replayRecord,
  220. });
  221. expect(replay?.isNetworkDetailsSetup()).toBeTruthy();
  222. });
  223. it.each([
  224. {
  225. data: {
  226. method: 'GET',
  227. request: {headers: {accept: 'application/json'}},
  228. },
  229. expected: true,
  230. },
  231. {
  232. data: {
  233. method: 'GET',
  234. },
  235. expected: false,
  236. },
  237. ])('should have isNetworkDetailsSetup=$expected', ({data, expected}) => {
  238. const startTimestamp = new Date();
  239. const endTimestamp = new Date();
  240. const replay = ReplayReader.factory({
  241. attachments: [
  242. TestStubs.Replay.SpanFrameEvent({
  243. timestamp: startTimestamp,
  244. data: {
  245. payload: TestStubs.Replay.RequestFrame({
  246. op: 'resource.fetch',
  247. startTimestamp,
  248. endTimestamp,
  249. description: '/api/0/issues/',
  250. data,
  251. }),
  252. },
  253. }),
  254. ],
  255. errors: [],
  256. replayRecord,
  257. });
  258. expect(replay?.isNetworkDetailsSetup()).toBe(expected);
  259. });
  260. });
  261. });