@elastic/synthetics 1.0.0-beta.4 → 1.0.0-beta.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/README.md +4 -3
  2. package/dist/cli.js +137 -160
  3. package/dist/cli.js.map +1 -1
  4. package/dist/common_types.d.ts +161 -31
  5. package/dist/common_types.d.ts.map +1 -1
  6. package/dist/config.d.ts +3 -5
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +7 -6
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/browser-service.d.ts +1 -1
  11. package/dist/core/browser-service.d.ts.map +1 -1
  12. package/dist/core/browser-service.js +7 -6
  13. package/dist/core/browser-service.js.map +1 -1
  14. package/dist/core/expect.d.ts +25 -25
  15. package/dist/core/expect.d.ts.map +1 -1
  16. package/dist/core/expect.js.map +1 -1
  17. package/dist/core/gatherer.d.ts +4 -8
  18. package/dist/core/gatherer.d.ts.map +1 -1
  19. package/dist/core/gatherer.js +53 -15
  20. package/dist/core/gatherer.js.map +1 -1
  21. package/dist/core/index.d.ts +5 -1
  22. package/dist/core/index.d.ts.map +1 -1
  23. package/dist/core/index.js +23 -11
  24. package/dist/core/index.js.map +1 -1
  25. package/dist/core/logger.d.ts +0 -1
  26. package/dist/core/logger.d.ts.map +1 -1
  27. package/dist/core/logger.js +3 -16
  28. package/dist/core/logger.js.map +1 -1
  29. package/dist/core/runner.d.ts +12 -63
  30. package/dist/core/runner.d.ts.map +1 -1
  31. package/dist/core/runner.js +156 -90
  32. package/dist/core/runner.js.map +1 -1
  33. package/dist/core/transform.d.ts +37 -0
  34. package/dist/core/transform.d.ts.map +1 -0
  35. package/dist/core/transform.js +148 -0
  36. package/dist/core/transform.js.map +1 -0
  37. package/dist/dsl/index.js +5 -1
  38. package/dist/dsl/index.js.map +1 -1
  39. package/dist/dsl/journey.d.ts +9 -4
  40. package/dist/dsl/journey.d.ts.map +1 -1
  41. package/dist/dsl/journey.js +37 -9
  42. package/dist/dsl/journey.js.map +1 -1
  43. package/dist/dsl/monitor.d.ts +83 -0
  44. package/dist/dsl/monitor.d.ts.map +1 -0
  45. package/dist/dsl/monitor.js +108 -0
  46. package/dist/dsl/monitor.js.map +1 -0
  47. package/dist/dsl/step.d.ts +3 -2
  48. package/dist/dsl/step.d.ts.map +1 -1
  49. package/dist/dsl/step.js +6 -1
  50. package/dist/dsl/step.js.map +1 -1
  51. package/dist/formatter/javascript.d.ts +98 -0
  52. package/dist/formatter/javascript.d.ts.map +1 -0
  53. package/dist/formatter/javascript.js +298 -0
  54. package/dist/formatter/javascript.js.map +1 -0
  55. package/dist/generator/index.d.ts +24 -0
  56. package/dist/generator/index.d.ts.map +1 -0
  57. package/dist/generator/index.js +241 -0
  58. package/dist/generator/index.js.map +1 -0
  59. package/dist/{reporters/reporter.js → generator/utils.d.ts} +5 -3
  60. package/dist/generator/utils.d.ts.map +1 -0
  61. package/dist/generator/utils.js +84 -0
  62. package/dist/generator/utils.js.map +1 -0
  63. package/dist/helpers.d.ts +28 -3
  64. package/dist/helpers.d.ts.map +1 -1
  65. package/dist/helpers.js +152 -20
  66. package/dist/helpers.js.map +1 -1
  67. package/dist/index.d.ts +6 -7
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +5 -26
  70. package/dist/index.js.map +1 -1
  71. package/dist/loader.d.ts +34 -0
  72. package/dist/loader.d.ts.map +1 -0
  73. package/dist/loader.js +149 -0
  74. package/dist/loader.js.map +1 -0
  75. package/dist/locations/index.d.ts +45 -0
  76. package/dist/locations/index.d.ts.map +1 -0
  77. package/dist/locations/index.js +83 -0
  78. package/dist/locations/index.js.map +1 -0
  79. package/dist/{reporters/reporter.d.ts → locations/public-locations.d.ts} +12 -8
  80. package/dist/locations/public-locations.d.ts.map +1 -0
  81. package/dist/locations/public-locations.js +41 -0
  82. package/dist/locations/public-locations.js.map +1 -0
  83. package/dist/options.d.ts +41 -0
  84. package/dist/options.d.ts.map +1 -0
  85. package/dist/options.js +198 -0
  86. package/dist/options.js.map +1 -0
  87. package/dist/plugins/browser-console.d.ts +5 -4
  88. package/dist/plugins/browser-console.d.ts.map +1 -1
  89. package/dist/plugins/browser-console.js +44 -24
  90. package/dist/plugins/browser-console.js.map +1 -1
  91. package/dist/plugins/index.js +5 -1
  92. package/dist/plugins/index.js.map +1 -1
  93. package/dist/plugins/network.d.ts +20 -33
  94. package/dist/plugins/network.d.ts.map +1 -1
  95. package/dist/plugins/network.js +177 -216
  96. package/dist/plugins/network.js.map +1 -1
  97. package/dist/plugins/performance.d.ts +4 -19
  98. package/dist/plugins/performance.d.ts.map +1 -1
  99. package/dist/plugins/performance.js +6 -5
  100. package/dist/plugins/performance.js.map +1 -1
  101. package/dist/plugins/plugin-manager.d.ts +9 -5
  102. package/dist/plugins/plugin-manager.d.ts.map +1 -1
  103. package/dist/plugins/plugin-manager.js +46 -30
  104. package/dist/plugins/plugin-manager.js.map +1 -1
  105. package/dist/plugins/tracing.d.ts +6 -6
  106. package/dist/plugins/tracing.d.ts.map +1 -1
  107. package/dist/plugins/tracing.js +10 -7
  108. package/dist/plugins/tracing.js.map +1 -1
  109. package/dist/push/bundler.d.ts +35 -0
  110. package/dist/push/bundler.d.ts.map +1 -0
  111. package/dist/push/bundler.js +131 -0
  112. package/dist/push/bundler.js.map +1 -0
  113. package/dist/push/index.d.ts +9 -0
  114. package/dist/push/index.d.ts.map +1 -0
  115. package/dist/push/index.js +227 -0
  116. package/dist/push/index.js.map +1 -0
  117. package/dist/push/kibana_api.d.ts +56 -0
  118. package/dist/push/kibana_api.d.ts.map +1 -0
  119. package/dist/push/kibana_api.js +135 -0
  120. package/dist/push/kibana_api.js.map +1 -0
  121. package/dist/push/monitor.d.ts +51 -0
  122. package/dist/push/monitor.d.ts.map +1 -0
  123. package/dist/push/monitor.js +244 -0
  124. package/dist/push/monitor.js.map +1 -0
  125. package/dist/{parse_args.d.ts → push/plugin.d.ts} +9 -6
  126. package/dist/push/plugin.d.ts.map +1 -0
  127. package/dist/push/plugin.js +66 -0
  128. package/dist/push/plugin.js.map +1 -0
  129. package/dist/push/request.d.ts +45 -0
  130. package/dist/push/request.d.ts.map +1 -0
  131. package/dist/push/request.js +109 -0
  132. package/dist/push/request.js.map +1 -0
  133. package/dist/push/utils.d.ts +31 -0
  134. package/dist/push/utils.d.ts.map +1 -0
  135. package/dist/push/utils.js +64 -0
  136. package/dist/push/utils.js.map +1 -0
  137. package/dist/reporters/base.d.ts +14 -6
  138. package/dist/reporters/base.d.ts.map +1 -1
  139. package/dist/reporters/base.js +58 -50
  140. package/dist/reporters/base.js.map +1 -1
  141. package/dist/reporters/index.d.ts +18 -8
  142. package/dist/reporters/index.d.ts.map +1 -1
  143. package/dist/reporters/index.js.map +1 -1
  144. package/dist/reporters/json.d.ts +17 -62
  145. package/dist/reporters/json.d.ts.map +1 -1
  146. package/dist/reporters/json.js +149 -179
  147. package/dist/reporters/json.js.map +1 -1
  148. package/dist/reporters/junit.d.ts +7 -1
  149. package/dist/reporters/junit.d.ts.map +1 -1
  150. package/dist/reporters/junit.js +90 -95
  151. package/dist/reporters/junit.js.map +1 -1
  152. package/dist/sdk/lh-trace-processor.d.ts +35 -9
  153. package/dist/sdk/lh-trace-processor.d.ts.map +1 -1
  154. package/dist/sdk/lh-trace-processor.js +114 -53
  155. package/dist/sdk/lh-trace-processor.js.map +1 -1
  156. package/dist/sdk/trace-metrics.d.ts +4 -4
  157. package/dist/sdk/trace-metrics.d.ts.map +1 -1
  158. package/dist/sdk/trace-metrics.js +3 -4
  159. package/dist/sdk/trace-metrics.js.map +1 -1
  160. package/dist/sdk/trace-processor.d.ts +11 -3
  161. package/dist/sdk/trace-processor.d.ts.map +1 -1
  162. package/dist/sdk/trace-processor.js +17 -9
  163. package/dist/sdk/trace-processor.js.map +1 -1
  164. package/package.json +39 -24
  165. package/src/cli.ts +219 -151
  166. package/src/common_types.ts +193 -31
  167. package/src/config.ts +3 -6
  168. package/src/core/expect.ts +58 -24
  169. package/src/core/gatherer.ts +64 -24
  170. package/src/core/index.ts +36 -15
  171. package/src/core/logger.ts +1 -12
  172. package/src/core/runner.ts +151 -136
  173. package/src/core/transform.ts +160 -0
  174. package/src/dsl/journey.ts +39 -6
  175. package/src/dsl/monitor.ts +140 -0
  176. package/src/dsl/step.ts +9 -2
  177. package/src/formatter/javascript.ts +367 -0
  178. package/src/generator/index.ts +308 -0
  179. package/src/generator/utils.ts +84 -0
  180. package/src/helpers.ts +159 -13
  181. package/src/index.ts +22 -22
  182. package/src/loader.ts +167 -0
  183. package/src/locations/index.ts +95 -0
  184. package/src/{reporters/reporter.ts → locations/public-locations.ts} +12 -9
  185. package/src/options.ts +227 -0
  186. package/src/plugins/browser-console.ts +30 -8
  187. package/src/plugins/network.ts +208 -267
  188. package/src/plugins/performance.ts +6 -22
  189. package/src/plugins/plugin-manager.ts +49 -31
  190. package/src/plugins/tracing.ts +8 -9
  191. package/src/push/bundler.ts +112 -0
  192. package/src/push/index.ts +282 -0
  193. package/src/push/kibana_api.ts +191 -0
  194. package/src/push/monitor.ts +276 -0
  195. package/src/push/plugin.ts +66 -0
  196. package/src/push/request.ts +141 -0
  197. package/src/push/utils.ts +77 -0
  198. package/src/reporters/base.ts +63 -52
  199. package/src/reporters/index.ts +26 -3
  200. package/src/reporters/json.ts +183 -202
  201. package/src/reporters/junit.ts +98 -93
  202. package/src/sdk/lh-trace-processor.ts +132 -68
  203. package/src/sdk/trace-metrics.ts +8 -4
  204. package/src/sdk/trace-processor.ts +28 -13
  205. package/templates/.github/workflows/run-synthetics.yml +27 -0
  206. package/templates/README.md +21 -0
  207. package/templates/journeys/advanced-example-helpers.ts +54 -0
  208. package/templates/journeys/advanced-example.journey.ts +43 -0
  209. package/templates/journeys/example.journey.ts +18 -0
  210. package/templates/lightweight/heartbeat.yml +8 -0
  211. package/templates/synthetics.config.ts +35 -0
  212. package/CHANGELOG.md +0 -131
  213. package/NOTICE.txt +0 -221
  214. package/dist/parse_args.d.ts.map +0 -1
  215. package/dist/parse_args.js +0 -95
  216. package/dist/parse_args.js.map +0 -1
  217. package/dist/reporters/reporter.d.ts.map +0 -1
  218. package/dist/reporters/reporter.js.map +0 -1
  219. package/dist/tsconfig.tsbuildinfo +0 -1888
  220. package/src/parse_args.ts +0 -135
@@ -23,220 +23,185 @@
23
23
  *
24
24
  */
25
25
 
26
- import { CDPSession } from 'playwright-chromium';
27
- import { Protocol } from 'playwright-chromium/types/protocol';
28
- import { NetworkInfo, BrowserInfo } from '../common_types';
26
+ import { Page, Request, Response } from 'playwright-chromium';
27
+ import { NetworkInfo, BrowserInfo, Driver } from '../common_types';
29
28
  import { Step } from '../dsl';
30
29
  import { getTimestamp } from '../helpers';
31
30
 
31
+ /**
32
+ * Kibana UI expects the requestStartTime and loadEndTime to be baseline
33
+ * in seconds as they have the logic to convert it to milliseconds before
34
+ * using for offset calculation
35
+ */
36
+ function epochTimeInSeconds() {
37
+ return getTimestamp() / 1e6;
38
+ }
39
+
40
+ /**
41
+ * Used as a key in each Network Request to identify the
42
+ * associated request across distinct lifecycle events
43
+ */
44
+ export const NETWORK_ENTRY_SUMBOL = Symbol.for('NetworkEntry');
45
+
46
+ type RequestWithEntry = Request & {
47
+ NETWORK_ENTRY_SUMBOL?: symbol;
48
+ };
49
+
32
50
  export class NetworkManager {
33
51
  private _browser: BrowserInfo;
34
- private _InflightRequestMap = new Map<string, NetworkInfo>();
52
+ private _barrierPromises = new Set<Promise<void>>();
35
53
  results: Array<NetworkInfo> = [];
36
54
  _currentStep: Partial<Step> = null;
37
55
 
38
- async start(client: CDPSession) {
56
+ constructor(private driver: Driver) {}
57
+
58
+ /**
59
+ * Adds a protection barrier aganist all asynchronous extract operations from
60
+ * request/response object that are happening during page lifecycle, If the
61
+ * page is closed during the extraction, the barrier enforces those operations
62
+ * to not result in exception
63
+ */
64
+ private _addBarrier(page: Page, promise: Promise<void>) {
65
+ if (!page) return;
66
+ const race = Promise.race([
67
+ new Promise<void>(resolve =>
68
+ page.on('close', () => {
69
+ this._barrierPromises.delete(race);
70
+ resolve();
71
+ })
72
+ ),
73
+ promise,
74
+ ]);
75
+ this._barrierPromises.add(race);
76
+ race.then(() => this._barrierPromises.delete(race));
77
+ }
78
+
79
+ async start() {
80
+ const { client, context } = this.driver;
39
81
  const { product } = await client.send('Browser.getVersion');
40
82
  const [name, version] = product.split('/');
41
83
  this._browser = { name, version };
42
84
  await client.send('Network.enable');
43
85
  /**
44
- * Listen for all network events
86
+ * Listen for all network events from PW context
45
87
  */
46
- client.on(
47
- 'Network.requestWillBeSent',
48
- this._onRequestWillBeSent.bind(this)
49
- );
50
- client.on(
51
- 'Network.requestWillBeSentExtraInfo',
52
- this._onRequestWillBeSentExtraInfo.bind(this)
53
- );
54
- client.on('Network.dataReceived', this._onDataReceived.bind(this));
55
- client.on('Network.responseReceived', this._onResponseReceived.bind(this));
56
- client.on('Network.loadingFinished', this._onLoadingFinished.bind(this));
57
- client.on('Network.loadingFailed', this._onLoadingFailed.bind(this));
88
+ context.on('request', this._onRequest.bind(this));
89
+ context.on('response', this._onResponse.bind(this));
90
+ context.on('requestfinished', this._onRequestCompleted.bind(this));
91
+ context.on('requestfailed', this._onRequestCompleted.bind(this));
58
92
  }
59
93
 
60
- _findNetworkRecord(requestId: string) {
61
- return this._InflightRequestMap.get(requestId);
94
+ private _findNetworkEntry(
95
+ request: RequestWithEntry
96
+ ): NetworkInfo | undefined {
97
+ return request[NETWORK_ENTRY_SUMBOL];
62
98
  }
63
99
 
64
- _onRequestWillBeSent(event: Protocol.Network.requestWillBeSentPayload) {
65
- const { requestId, request, timestamp, type, loaderId } = event;
66
- const { url, method } = request;
100
+ private _onRequest(request: Request) {
101
+ const url = request.url();
67
102
  /**
68
103
  * Data URI should not show up as network requests
69
104
  */
70
105
  if (url.startsWith('data:')) {
71
106
  return;
72
107
  }
73
- const isNavigationRequest = requestId == loaderId && type === 'Document';
74
- const record = this._findNetworkRecord(requestId);
75
- /**
76
- * On redirects, another `requestWillBeSent` event will be fired for the
77
- * same requestId. We calculate the timings for the redirect request using
78
- * the `redirectedResponse` from the redirected request.
79
- */
80
- if (record) {
81
- if (event.redirectResponse) {
82
- const response = event.redirectResponse;
83
- const data = Object.assign(event, {
84
- type: event.type,
85
- response,
86
- encodedDataLength: response.encodedDataLength,
87
- });
88
- this._onResponseReceived(data);
89
- this._onLoadingFinished(data);
90
- } else {
91
- /**
92
- * Edge case, we handle it to proceed with next navigation
93
- */
94
- this._onLoadingFailed(
95
- Object.assign(event, {
96
- type: event.type,
97
- errorText: 'redirectResponse data is not available',
98
- })
99
- );
100
- }
101
- const redirectedRecord = this._findNetworkRecord(requestId);
102
- /**
103
- * Rewrite the map with new redirect id to not reset
104
- * the redirect request with original request
105
- */
106
- this._InflightRequestMap.delete(requestId);
107
- this._InflightRequestMap.set(
108
- `redirect:${timestamp}:${requestId}`,
109
- redirectedRecord
110
- );
111
- }
112
108
 
113
- this._InflightRequestMap.set(requestId, {
109
+ const timestamp = getTimestamp();
110
+ const networkEntry: NetworkInfo = {
114
111
  browser: this._browser,
115
112
  step: this._currentStep,
116
- timestamp: getTimestamp(),
113
+ timestamp,
117
114
  url,
118
- request,
119
- type,
120
- method,
121
- requestSentTime: timestamp,
122
- isNavigationRequest,
123
- status: 0,
115
+ type: request.resourceType(),
116
+ request: {
117
+ url,
118
+ method: request.method(),
119
+ headers: {},
120
+ body: {
121
+ bytes: request.postDataBuffer()?.length || 0,
122
+ },
123
+ },
124
+ response: {
125
+ status: -1,
126
+ headers: {},
127
+ mimeType: 'x-unknown',
128
+ redirectURL: '',
129
+ },
130
+ isNavigationRequest: request.isNavigationRequest(),
131
+ requestSentTime: epochTimeInSeconds(),
124
132
  loadEndTime: -1,
125
133
  responseReceivedTime: -1,
126
- response: null,
127
134
  resourceSize: 0,
128
135
  transferSize: 0,
129
136
  timings: null,
130
- });
131
- }
132
-
133
- _onRequestWillBeSentExtraInfo(
134
- event: Protocol.Network.requestWillBeSentExtraInfoPayload
135
- ) {
136
- const { requestId, headers } = event;
137
- const record = this._findNetworkRecord(requestId);
138
- if (!record) {
139
- return;
140
- }
141
- /**
142
- * Enhance request headers with additional information
143
- */
144
- record.request.headers = {
145
- ...record.request.headers,
146
- ...headers,
147
137
  };
148
- }
149
138
 
150
- _onDataReceived(event: Protocol.Network.dataReceivedPayload) {
151
- const { requestId, dataLength, encodedDataLength } = event;
152
- const record = this._findNetworkRecord(requestId);
153
- if (!record) {
154
- return;
139
+ if (request.redirectedFrom()) {
140
+ const fromEntry = this._findNetworkEntry(request.redirectedFrom());
141
+ if (fromEntry) fromEntry.response.redirectURL = request.url();
155
142
  }
156
- record.resourceSize += dataLength;
157
- if (encodedDataLength >= 0) record.transferSize += encodedDataLength;
158
- }
159
143
 
160
- _onResponseReceived(event: Protocol.Network.responseReceivedPayload) {
161
- const { requestId, response, timestamp } = event;
162
- const record = this._findNetworkRecord(requestId);
163
- if (!record) {
164
- return;
165
- }
166
- Object.assign(record, {
167
- status: response.status,
168
- response,
169
- responseReceivedTime: timestamp,
170
- transferSize: response.encodedDataLength,
171
- });
144
+ request[NETWORK_ENTRY_SUMBOL] = networkEntry;
145
+ this.results.push(networkEntry);
172
146
  }
173
147
 
174
- _onLoadingFinished(event: Protocol.Network.loadingFinishedPayload) {
175
- const { requestId, timestamp, encodedDataLength } = event;
176
- this._requestCompleted(requestId, timestamp, encodedDataLength);
177
- }
148
+ private async _onResponse(response: Response) {
149
+ const request = response.request();
150
+ const networkEntry = this._findNetworkEntry(request);
151
+ if (!networkEntry) return;
178
152
 
179
- _onLoadingFailed(event: Protocol.Network.loadingFailedPayload) {
180
- const { requestId, timestamp } = event;
181
- this._requestCompleted(requestId, timestamp);
182
- }
153
+ networkEntry.responseReceivedTime = epochTimeInSeconds();
154
+ networkEntry.response = {
155
+ url: response.url(),
156
+ status: response.status(),
157
+ statusText: response.statusText(),
158
+ headers: {},
159
+ mimeType: 'x-unknown',
160
+ redirectURL: networkEntry.response.redirectURL,
161
+ };
183
162
 
184
- _requestCompleted(
185
- requestId: string,
186
- endTime: number,
187
- encodedDataLength?: number
188
- ) {
189
- const record = this._findNetworkRecord(requestId);
190
- if (!record) {
191
- return;
192
- }
193
- if (encodedDataLength >= 0) {
194
- record.transferSize = encodedDataLength;
195
- }
196
- record.loadEndTime = endTime;
197
- record.timings = this.calculateTimings(record);
198
- this.results.push(record);
199
- /**
200
- * When there is DNS failure, browsers keeps retrying requests which
201
- * might result in incorrect timings so we get remove them to measure only once
202
- */
203
- this._InflightRequestMap.delete(requestId);
204
- }
163
+ const page = request.frame().page();
164
+ this._addBarrier(
165
+ page,
166
+ request.allHeaders().then(reqHeaders => {
167
+ networkEntry.request.headers = reqHeaders;
168
+ networkEntry.request.referrer = reqHeaders?.referer;
169
+ })
170
+ );
171
+ this._addBarrier(
172
+ page,
173
+ response.serverAddr().then(server => {
174
+ networkEntry.response.remoteIPAddress = server?.ipAddress;
175
+ networkEntry.response.remotePort = server?.port;
176
+ })
177
+ );
178
+ this._addBarrier(
179
+ page,
180
+ response.securityDetails().then(details => {
181
+ if (details) networkEntry.response.securityDetails = details;
182
+ })
183
+ );
184
+ this._addBarrier(
185
+ page,
186
+ response.allHeaders().then(resHeaders => {
187
+ networkEntry.response.headers = resHeaders;
205
188
 
206
- /**
207
- * Account for missing response received event and also adjust the
208
- * response received event based on when first byte event was recorded
209
- */
210
- getResponseReceivedTime(
211
- timing: Protocol.Network.Response['timing'],
212
- responseReceivedTime: number
213
- ) {
214
- if (timing == null) {
215
- return responseReceivedTime;
216
- }
217
- const startTime = timing.requestTime;
218
- const headersReceivedTime = startTime + timing.receiveHeadersEnd / 1000;
219
- if (
220
- responseReceivedTime < 0 ||
221
- responseReceivedTime > headersReceivedTime
222
- ) {
223
- responseReceivedTime = headersReceivedTime;
224
- }
225
- if (startTime > responseReceivedTime) {
226
- responseReceivedTime = startTime;
227
- }
228
- return responseReceivedTime;
189
+ const contentType = resHeaders['content-type'];
190
+ if (contentType)
191
+ networkEntry.response.mimeType = contentType.split(';')[0];
192
+ })
193
+ );
229
194
  }
230
195
 
231
- /**
232
- * The timing calculations are based on the chrome devtools frontend
233
- * https://github.com/ChromeDevTools/devtools-frontend/blob/7f5478d8ceb7586f23f4073ab5c2085dac1ec26a/front_end/network/RequestTimingView.js#L98-L193
234
- */
235
- calculateTimings(record: NetworkInfo) {
236
- const result: NetworkInfo['timings'] = {
196
+ private async _onRequestCompleted(request: Request) {
197
+ const networkEntry = this._findNetworkEntry(request);
198
+ if (!networkEntry) return;
199
+
200
+ networkEntry.loadEndTime = epochTimeInSeconds();
201
+ const timing = request.timing();
202
+ const { loadEndTime, requestSentTime } = networkEntry;
203
+ networkEntry.timings = {
237
204
  blocked: -1,
238
- queueing: -1,
239
- proxy: -1,
240
205
  dns: -1,
241
206
  ssl: -1,
242
207
  connect: -1,
@@ -245,28 +210,7 @@ export class NetworkManager {
245
210
  receive: -1,
246
211
  total: -1,
247
212
  };
248
- const { requestSentTime, loadEndTime, responseReceivedTime } = record;
249
213
 
250
- /**
251
- * Handle when request failed and no responseReceived event was
252
- * fired for that particular request
253
- * Eg: connection refused by remote host
254
- */
255
- const response = record.response || {
256
- timing: null,
257
- fromServiceWorker: false,
258
- };
259
-
260
- const toMilliseconds = (time: number) => (time === -1 ? -1 : time * 1000);
261
- const calculateDiffInMs = (
262
- name: keyof NetworkInfo['timings'],
263
- start: number,
264
- end: number
265
- ) => {
266
- if (start < Number.MAX_VALUE && start <= end) {
267
- result[name] = toMilliseconds(end - start);
268
- }
269
- };
270
214
  const firstPositive = (numbers: number[]) => {
271
215
  for (let i = 0; i < numbers.length; ++i) {
272
216
  if (numbers[i] > 0) {
@@ -275,91 +219,88 @@ export class NetworkManager {
275
219
  }
276
220
  return null;
277
221
  };
278
- /**
279
- * requestTime is baseline in seconds, rest of the timing data are ticks in milliseconds
280
- * from the requestTime, so we calculate the offset from that
281
- */
282
- const addOffsetRange = (
283
- name: keyof NetworkInfo['timings'],
284
- start: number,
285
- end: number
286
- ) => {
287
- if (start >= 0 && end >= 0) {
288
- calculateDiffInMs(
289
- name,
290
- startTime + start / 1000,
291
- startTime + end / 1000
292
- );
293
- }
222
+ const roundMilliSecs = (value: number): number => {
223
+ return Math.floor(value * 1000) / 1000;
294
224
  };
295
- const timing = response.timing;
296
- const actResRcvdTime = this.getResponseReceivedTime(
297
- timing,
298
- responseReceivedTime
299
- );
300
- const issueTime = requestSentTime;
301
- const startTime = timing == null ? -1 : timing.requestTime;
302
- const endTime = firstPositive([loadEndTime, actResRcvdTime]) || startTime;
303
225
 
304
- if (timing == null) {
305
- const start =
306
- issueTime !== -1 ? issueTime : startTime !== -1 ? startTime : 0;
307
- const middle = actResRcvdTime === -1 ? Number.MAX_VALUE : actResRcvdTime;
308
- const end = endTime === -1 ? Number.MAX_VALUE : endTime;
309
- calculateDiffInMs('total', start, end);
310
- calculateDiffInMs('blocked', start, middle);
311
- calculateDiffInMs('receive', middle, end);
312
- // Check if the request is blocked/stalled for the whole timeframe and
313
- // calculate timings appropriately for that request
314
- if (!isFinite(result['blocked'])) {
315
- result['blocked'] = result['total'];
316
- }
317
- return result;
318
- }
319
- calculateDiffInMs(
320
- 'total',
321
- issueTime < startTime ? issueTime : startTime,
322
- endTime
323
- );
324
- if (issueTime < startTime) {
325
- calculateDiffInMs('queueing', issueTime, startTime);
226
+ if (timing.startTime === 0) {
227
+ // Convert to milliseconds before round off
228
+ const total = roundMilliSecs((loadEndTime - requestSentTime) * 1000);
229
+ networkEntry.timings.total = total;
230
+ return;
326
231
  }
327
- const responseReceived = toMilliseconds(actResRcvdTime - startTime);
328
232
 
329
- if (response.fromServiceWorker) {
330
- addOffsetRange('blocked', 0, timing.workerStart);
331
- addOffsetRange('wait', timing.sendEnd, responseReceived);
332
- } else if (!timing.pushStart) {
333
- const blockingEnd =
233
+ const blocked =
234
+ roundMilliSecs(
334
235
  firstPositive([
335
- timing.dnsStart,
236
+ timing.domainLookupStart,
336
237
  timing.connectStart,
337
- timing.sendStart,
338
- responseReceived,
339
- ]) || 0;
340
- addOffsetRange('blocked', 0, blockingEnd);
341
- addOffsetRange('proxy', timing.proxyStart, timing.proxyEnd);
342
- addOffsetRange('dns', timing.dnsStart, timing.dnsEnd);
343
- addOffsetRange('connect', timing.connectStart, timing.connectEnd);
344
- addOffsetRange('ssl', timing.sslStart, timing.sslEnd);
345
- addOffsetRange('send', timing.sendStart, timing.sendEnd);
346
- addOffsetRange(
347
- 'wait',
348
- Math.max(
349
- timing.sendEnd,
350
- timing.connectEnd,
351
- timing.dnsEnd,
352
- timing.proxyEnd,
353
- blockingEnd
354
- ),
355
- responseReceived
356
- );
357
- }
358
- calculateDiffInMs('receive', actResRcvdTime, endTime);
359
- return result;
238
+ timing.requestStart,
239
+ ])
240
+ ) || -1;
241
+ const dns =
242
+ timing.domainLookupEnd !== -1
243
+ ? roundMilliSecs(timing.domainLookupEnd - timing.domainLookupStart)
244
+ : -1;
245
+ const connect =
246
+ timing.connectEnd !== -1
247
+ ? roundMilliSecs(timing.connectEnd - timing.connectStart)
248
+ : -1;
249
+ const ssl =
250
+ timing.secureConnectionStart !== -1
251
+ ? roundMilliSecs(timing.connectEnd - timing.secureConnectionStart)
252
+ : -1;
253
+ const wait =
254
+ timing.responseStart !== -1
255
+ ? roundMilliSecs(timing.responseStart - timing.requestStart)
256
+ : -1;
257
+ const receive =
258
+ timing.responseEnd !== -1
259
+ ? roundMilliSecs(timing.responseEnd - timing.responseStart)
260
+ : -1;
261
+ const total = [blocked, dns, connect, wait, receive].reduce(
262
+ (pre, cur) => (cur > 0 ? cur + pre : pre),
263
+ 0
264
+ );
265
+ networkEntry.timings = {
266
+ blocked,
267
+ dns,
268
+ connect,
269
+ ssl,
270
+ wait,
271
+ send: 0, // not exposed via RT api
272
+ receive,
273
+ total,
274
+ };
275
+
276
+ const page = request.frame().page();
277
+ // For aborted/failed requests sizes will not be present
278
+ this._addBarrier(
279
+ page,
280
+ request.sizes().then(sizes => {
281
+ networkEntry.request.bytes =
282
+ sizes.requestHeadersSize + sizes.requestBodySize;
283
+ networkEntry.request.body = {
284
+ bytes: sizes.requestBodySize,
285
+ };
286
+ const transferSize = sizes.responseHeadersSize + sizes.responseBodySize;
287
+ networkEntry.transferSize = transferSize;
288
+ networkEntry.response.bytes = transferSize;
289
+ networkEntry.response.body = {
290
+ bytes: sizes.responseBodySize,
291
+ };
292
+ })
293
+ );
360
294
  }
361
295
 
362
- stop() {
296
+ async stop() {
297
+ await Promise.all(this._barrierPromises);
298
+ const context = this.driver.context;
299
+ context.off('request', this._onRequest.bind(this));
300
+ context.off('response', this._onResponse.bind(this));
301
+ context.off('requestfinished', this._onRequestCompleted.bind(this));
302
+ context.off('requestfailed', this._onRequestCompleted.bind(this));
303
+ this._barrierPromises.clear();
363
304
  return this.results;
364
305
  }
365
306
  }
@@ -23,7 +23,7 @@
23
23
  *
24
24
  */
25
25
 
26
- import { CDPSession } from 'playwright-chromium';
26
+ import { Driver, PageMetrics } from '../common_types';
27
27
 
28
28
  const supportedMetrics = new Set<string>([
29
29
  'Timestamp',
@@ -41,39 +41,23 @@ const supportedMetrics = new Set<string>([
41
41
  'JSHeapTotalSize',
42
42
  ]);
43
43
 
44
- export interface Metrics {
45
- Timestamp?: number;
46
- Documents?: number;
47
- Frames?: number;
48
- JSEventListeners?: number;
49
- Nodes?: number;
50
- LayoutCount?: number;
51
- RecalcStyleCount?: number;
52
- LayoutDuration?: number;
53
- RecalcStyleDuration?: number;
54
- ScriptDuration?: number;
55
- TaskDuration?: number;
56
- JSHeapUsedSize?: number;
57
- JSHeapTotalSize?: number;
58
- }
59
-
60
44
  export class PerformanceManager {
61
- constructor(private client: CDPSession) {}
45
+ constructor(private driver: Driver) {}
62
46
 
63
47
  async start() {
64
- await this.client.send('Performance.enable');
48
+ await this.driver.client.send('Performance.enable');
65
49
  }
66
50
 
67
51
  async stop() {
68
- await this.client.send('Performance.disable');
52
+ await this.driver.client.send('Performance.disable');
69
53
  }
70
54
 
71
55
  async getMetrics() {
72
- const { metrics } = await this.client.send('Performance.getMetrics');
56
+ const { metrics } = await this.driver.client.send('Performance.getMetrics');
73
57
  return this.buildMetricsObject(metrics);
74
58
  }
75
59
 
76
- private buildMetricsObject(metrics = []): Metrics {
60
+ private buildMetricsObject(metrics = []): PageMetrics {
77
61
  const result = {};
78
62
  for (const metric of metrics) {
79
63
  if (supportedMetrics.has(metric.name)) result[metric.name] = metric.value;