replayReader.spec.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  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 optionsFrame = TestStubs.Replay.OptionFrame({});
  58. const optionsEvent = TestStubs.Replay.OptionFrameEvent({
  59. timestamp,
  60. data: {payload: optionsFrame},
  61. });
  62. const firstDiv = TestStubs.Replay.RRWebFullSnapshotFrameEvent({timestamp});
  63. const secondDiv = TestStubs.Replay.RRWebFullSnapshotFrameEvent({timestamp});
  64. const clickEvent = TestStubs.Replay.ClickEvent({timestamp});
  65. const firstMemory = TestStubs.Replay.MemoryEvent({
  66. startTimestamp: timestamp,
  67. endTimestamp: timestamp,
  68. });
  69. const secondMemory = TestStubs.Replay.MemoryEvent({
  70. startTimestamp: timestamp,
  71. endTimestamp: timestamp,
  72. });
  73. const navigationEvent = TestStubs.Replay.NavigateEvent({
  74. startTimestamp: new Date('2023-12-25T00:03:00'),
  75. endTimestamp: new Date('2023-12-25T00:03:30'),
  76. });
  77. const consoleEvent = TestStubs.Replay.ConsoleEvent({timestamp});
  78. const customEvent = TestStubs.Replay.BreadcrumbFrameEvent({
  79. timestamp: new Date('2023-12-25T00:02:30'),
  80. data: {
  81. payload: {
  82. category: 'redux.action',
  83. data: {
  84. action: 'save.click',
  85. },
  86. message: '',
  87. timestamp: new Date('2023-12-25T00:02:30').getTime() / 1000,
  88. type: BreadcrumbType.DEFAULT,
  89. },
  90. },
  91. });
  92. const attachments = [
  93. clickEvent,
  94. consoleEvent,
  95. firstDiv,
  96. firstMemory,
  97. navigationEvent,
  98. optionsEvent,
  99. secondDiv,
  100. secondMemory,
  101. customEvent,
  102. ];
  103. it.each([
  104. {
  105. method: 'getRRWebFrames',
  106. expected: [
  107. {
  108. type: EventType.Custom,
  109. timestamp: expect.any(Number),
  110. data: {tag: 'replay.start', payload: {}},
  111. },
  112. firstDiv,
  113. secondDiv,
  114. {
  115. type: EventType.Custom,
  116. timestamp: expect.any(Number),
  117. data: {tag: 'replay.end', payload: {}},
  118. },
  119. ],
  120. },
  121. {
  122. method: 'getConsoleFrames',
  123. expected: [expect.objectContaining({category: 'console'})],
  124. },
  125. {
  126. method: 'getNetworkFrames',
  127. expected: [expect.objectContaining({op: 'navigation.navigate'})],
  128. },
  129. {
  130. method: 'getDOMFrames',
  131. expected: [expect.objectContaining({category: 'ui.click'})],
  132. },
  133. {
  134. method: 'getMemoryFrames',
  135. expected: [
  136. expect.objectContaining({op: 'memory'}),
  137. expect.objectContaining({op: 'memory'}),
  138. ],
  139. },
  140. {
  141. method: 'getChapterFrames',
  142. expected: [
  143. expect.objectContaining({category: 'replay.init'}),
  144. expect.objectContaining({category: 'ui.click'}),
  145. expect.objectContaining({category: 'redux.action'}),
  146. expect.objectContaining({op: 'navigation.navigate'}),
  147. ],
  148. },
  149. {
  150. method: 'getTimelineFrames',
  151. expected: [
  152. expect.objectContaining({category: 'replay.init'}),
  153. expect.objectContaining({category: 'ui.click'}),
  154. expect.objectContaining({op: 'navigation.navigate'}),
  155. ],
  156. },
  157. {
  158. method: 'getSDKOptions',
  159. expected: optionsFrame,
  160. },
  161. ])('Calling $method will filter frames', ({method, expected}) => {
  162. const replay = ReplayReader.factory({
  163. attachments,
  164. errors: [],
  165. replayRecord,
  166. });
  167. const exec = replay?.[method];
  168. expect(exec()).toStrictEqual(expected);
  169. });
  170. });
  171. it('shoud return the SDK config if there is a RecordingOptions event found', () => {
  172. const timestamp = new Date();
  173. const optionsFrame = TestStubs.Replay.OptionFrame({});
  174. const replay = ReplayReader.factory({
  175. attachments: [
  176. TestStubs.Replay.OptionFrameEvent({
  177. timestamp,
  178. data: {payload: optionsFrame},
  179. }),
  180. ],
  181. errors: [],
  182. replayRecord,
  183. });
  184. expect(replay?.sdkConfig()).toBe(optionsFrame);
  185. });
  186. describe('isNetworkDetailsSetup', () => {
  187. it('should have isNetworkDetailsSetup=true if sdkConfig says so', () => {
  188. const timestamp = new Date();
  189. const replay = ReplayReader.factory({
  190. attachments: [
  191. TestStubs.Replay.OptionFrameEvent({
  192. timestamp,
  193. data: {
  194. payload: TestStubs.Replay.OptionFrame({
  195. networkDetailHasUrls: true,
  196. }),
  197. },
  198. }),
  199. ],
  200. errors: [],
  201. replayRecord,
  202. });
  203. expect(replay?.isNetworkDetailsSetup()).toBeTruthy();
  204. });
  205. it.each([
  206. {
  207. data: {
  208. method: 'GET',
  209. request: {headers: {accept: 'application/json'}},
  210. },
  211. expected: true,
  212. },
  213. {
  214. data: {
  215. method: 'GET',
  216. },
  217. expected: false,
  218. },
  219. ])('should have isNetworkDetailsSetup=$expected', ({data, expected}) => {
  220. const startTimestamp = new Date();
  221. const endTimestamp = new Date();
  222. const replay = ReplayReader.factory({
  223. attachments: [
  224. TestStubs.Replay.SpanFrameEvent({
  225. timestamp: startTimestamp,
  226. data: {
  227. payload: TestStubs.Replay.RequestFrame({
  228. op: 'resource.fetch',
  229. startTimestamp,
  230. endTimestamp,
  231. description: '/api/0/issues/',
  232. data,
  233. }),
  234. },
  235. }),
  236. ],
  237. errors: [],
  238. replayRecord,
  239. });
  240. expect(replay?.isNetworkDetailsSetup()).toBe(expected);
  241. });
  242. });
  243. });