@elastic/synthetics 1.0.0-beta.8 → 1.0.0

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 +5 -4
  2. package/dist/cli.js +139 -165
  3. package/dist/cli.js.map +1 -1
  4. package/dist/common_types.d.ts +158 -34
  5. package/dist/common_types.d.ts.map +1 -1
  6. package/dist/config.d.ts +1 -0
  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 +5 -8
  18. package/dist/core/gatherer.d.ts.map +1 -1
  19. package/dist/core/gatherer.js +75 -17
  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 +10 -17
  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 +159 -92
  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 -7
  40. package/dist/dsl/journey.d.ts.map +1 -1
  41. package/dist/dsl/journey.js +32 -39
  42. package/dist/dsl/journey.js.map +1 -1
  43. package/dist/dsl/monitor.d.ts +93 -0
  44. package/dist/dsl/monitor.d.ts.map +1 -0
  45. package/dist/dsl/monitor.js +110 -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 +115 -0
  52. package/dist/formatter/javascript.d.ts.map +1 -0
  53. package/dist/formatter/javascript.js +331 -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 +36 -9
  64. package/dist/helpers.d.ts.map +1 -1
  65. package/dist/helpers.js +170 -24
  66. package/dist/helpers.js.map +1 -1
  67. package/dist/index.d.ts +5 -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 +153 -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 +42 -0
  84. package/dist/options.d.ts.map +1 -0
  85. package/dist/options.js +146 -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 +47 -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 +24 -31
  94. package/dist/plugins/network.d.ts.map +1 -1
  95. package/dist/plugins/network.js +226 -229
  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 +13 -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 +10 -0
  114. package/dist/push/index.d.ts.map +1 -0
  115. package/dist/push/index.js +235 -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 +55 -0
  122. package/dist/push/monitor.d.ts.map +1 -0
  123. package/dist/push/monitor.js +268 -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 +147 -177
  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 +38 -30
  165. package/src/cli.ts +223 -158
  166. package/src/common_types.ts +191 -34
  167. package/src/config.ts +1 -1
  168. package/src/core/expect.ts +58 -24
  169. package/src/core/gatherer.ts +92 -25
  170. package/src/core/index.ts +36 -15
  171. package/src/core/logger.ts +8 -12
  172. package/src/core/runner.ts +158 -148
  173. package/src/core/transform.ts +160 -0
  174. package/src/dsl/journey.ts +41 -21
  175. package/src/dsl/monitor.ts +152 -0
  176. package/src/dsl/step.ts +9 -2
  177. package/src/formatter/javascript.ts +414 -0
  178. package/src/generator/index.ts +308 -0
  179. package/src/generator/utils.ts +84 -0
  180. package/src/helpers.ts +182 -16
  181. package/src/index.ts +21 -22
  182. package/src/loader.ts +171 -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 +175 -0
  186. package/src/plugins/browser-console.ts +33 -8
  187. package/src/plugins/network.ts +266 -284
  188. package/src/plugins/performance.ts +6 -22
  189. package/src/plugins/plugin-manager.ts +49 -31
  190. package/src/plugins/tracing.ts +11 -9
  191. package/src/push/bundler.ts +112 -0
  192. package/src/push/index.ts +291 -0
  193. package/src/push/kibana_api.ts +191 -0
  194. package/src/push/monitor.ts +307 -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 +181 -201
  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 +9 -0
  211. package/templates/synthetics.config.ts +35 -0
  212. package/CHANGELOG.md +0 -181
  213. package/NOTICE.txt +0 -221
  214. package/dist/parse_args.d.ts.map +0 -1
  215. package/dist/parse_args.js +0 -98
  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 -1895
  220. package/src/parse_args.ts +0 -146
@@ -23,343 +23,325 @@
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';
28
+ import { log } from '../core/logger';
29
29
  import { Step } from '../dsl';
30
30
  import { getTimestamp } from '../helpers';
31
31
 
32
+ /**
33
+ * Kibana UI expects the requestStartTime and loadEndTime to be baseline
34
+ * in seconds as they have the logic to convert it to milliseconds before
35
+ * using for offset calculation
36
+ */
37
+ function epochTimeInSeconds() {
38
+ return getTimestamp() / 1e6;
39
+ }
40
+
41
+ /**
42
+ * Find the first positive number in an array for Resource timing data
43
+ */
44
+ const firstPositive = (numbers: number[]) => {
45
+ for (let i = 0; i < numbers.length; ++i) {
46
+ if (numbers[i] > 0) {
47
+ return numbers[i];
48
+ }
49
+ }
50
+ return null;
51
+ };
52
+
53
+ const roundMilliSecs = (value: number): number => {
54
+ return Math.floor(value * 1000) / 1000;
55
+ };
56
+
57
+ /**
58
+ * Used as a key in each Network Request to identify the
59
+ * associated request across distinct lifecycle events
60
+ */
61
+ export const NETWORK_ENTRY_SUMBOL = Symbol.for('NetworkEntry');
62
+
63
+ type RequestWithEntry = Request & {
64
+ NETWORK_ENTRY_SUMBOL?: symbol;
65
+ };
66
+
32
67
  export class NetworkManager {
33
68
  private _browser: BrowserInfo;
34
- private _InflightRequestMap = new Map<string, NetworkInfo>();
69
+ private _barrierPromises = new Set<Promise<void>>();
35
70
  results: Array<NetworkInfo> = [];
36
71
  _currentStep: Partial<Step> = null;
37
72
 
38
- async start(client: CDPSession) {
73
+ constructor(private driver: Driver) {}
74
+
75
+ /**
76
+ * Adds a protection barrier aganist all asynchronous extract operations from
77
+ * request/response object that are happening during page lifecycle, If the
78
+ * page is closed during the extraction, the barrier enforces those operations
79
+ * to not result in exception
80
+ */
81
+ private _addBarrier(page: Page, promise: Promise<void>) {
82
+ if (!page) return;
83
+ const race = Promise.race([
84
+ new Promise<void>(resolve =>
85
+ page.on('close', () => {
86
+ this._barrierPromises.delete(race);
87
+ resolve();
88
+ })
89
+ ),
90
+ promise,
91
+ ]);
92
+ this._barrierPromises.add(race);
93
+ race.then(() => this._barrierPromises.delete(race));
94
+ }
95
+
96
+ async start() {
97
+ log(`Plugins: started collecting network events`);
98
+ const { client, context } = this.driver;
39
99
  const { product } = await client.send('Browser.getVersion');
40
100
  const [name, version] = product.split('/');
41
101
  this._browser = { name, version };
42
- await client.send('Network.enable');
43
102
  /**
44
- * Listen for all network events
103
+ * Listen for all network events from PW context
45
104
  */
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));
105
+ context.on('request', this._onRequest.bind(this));
106
+ context.on('response', this._onResponse.bind(this));
107
+ context.on('requestfinished', this._onRequestCompleted.bind(this));
108
+ context.on('requestfailed', this._onRequestCompleted.bind(this));
58
109
  }
59
110
 
60
- _findNetworkRecord(requestId: string) {
61
- return this._InflightRequestMap.get(requestId);
111
+ private _findNetworkEntry(
112
+ request: RequestWithEntry
113
+ ): NetworkInfo | undefined {
114
+ return request[NETWORK_ENTRY_SUMBOL];
62
115
  }
63
116
 
64
- _onRequestWillBeSent(event: Protocol.Network.requestWillBeSentPayload) {
65
- const { requestId, request, timestamp, type, loaderId } = event;
66
- const { url, method } = request;
117
+ private _onRequest(request: Request) {
118
+ const url = request.url();
67
119
  /**
68
120
  * Data URI should not show up as network requests
69
121
  */
70
122
  if (url.startsWith('data:')) {
71
123
  return;
72
124
  }
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
125
 
113
- this._InflightRequestMap.set(requestId, {
126
+ const timestamp = getTimestamp();
127
+ const networkEntry: NetworkInfo = {
114
128
  browser: this._browser,
115
129
  step: this._currentStep,
116
- timestamp: getTimestamp(),
130
+ timestamp,
117
131
  url,
118
- request,
119
- type,
120
- method,
121
- requestSentTime: timestamp,
122
- isNavigationRequest,
123
- status: 0,
132
+ type: request.resourceType(),
133
+ request: {
134
+ url,
135
+ method: request.method(),
136
+ headers: request.headers(),
137
+ body: {
138
+ bytes: request.postDataBuffer()?.length || 0,
139
+ },
140
+ },
141
+ response: {
142
+ status: -1,
143
+ headers: {},
144
+ mimeType: 'x-unknown',
145
+ redirectURL: '',
146
+ },
147
+ isNavigationRequest: request.isNavigationRequest(),
148
+ requestSentTime: epochTimeInSeconds(),
124
149
  loadEndTime: -1,
125
150
  responseReceivedTime: -1,
126
- response: null,
127
151
  resourceSize: 0,
128
152
  transferSize: 0,
129
- 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,
153
+ timings: {
154
+ blocked: -1,
155
+ dns: -1,
156
+ ssl: -1,
157
+ connect: -1,
158
+ send: -1,
159
+ wait: -1,
160
+ receive: -1,
161
+ total: -1,
162
+ },
147
163
  };
148
- }
149
164
 
150
- _onDataReceived(event: Protocol.Network.dataReceivedPayload) {
151
- const { requestId, dataLength, encodedDataLength } = event;
152
- const record = this._findNetworkRecord(requestId);
153
- if (!record) {
154
- return;
165
+ if (request.redirectedFrom()) {
166
+ const fromEntry = this._findNetworkEntry(request.redirectedFrom());
167
+ if (fromEntry) fromEntry.response.redirectURL = request.url();
155
168
  }
156
- record.resourceSize += dataLength;
157
- if (encodedDataLength >= 0) record.transferSize += encodedDataLength;
158
- }
159
169
 
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
- });
170
+ request[NETWORK_ENTRY_SUMBOL] = networkEntry;
171
+ this.results.push(networkEntry);
172
172
  }
173
173
 
174
- _onLoadingFinished(event: Protocol.Network.loadingFinishedPayload) {
175
- const { requestId, timestamp, encodedDataLength } = event;
176
- this._requestCompleted(requestId, timestamp, encodedDataLength);
177
- }
174
+ private async _onResponse(response: Response) {
175
+ const request = response.request();
176
+ const networkEntry = this._findNetworkEntry(request);
177
+ if (!networkEntry) return;
178
+
179
+ networkEntry.responseReceivedTime = epochTimeInSeconds();
180
+ networkEntry.response = {
181
+ url: response.url(),
182
+ status: response.status(),
183
+ statusText: response.statusText(),
184
+ headers: {},
185
+ mimeType: 'x-unknown',
186
+ redirectURL: networkEntry.response.redirectURL,
187
+ };
188
+
189
+ // Gather all resource timing information up until the
190
+ // TTFB(Time to first byte) is received
191
+ const timing = request.timing();
192
+ const blocked =
193
+ roundMilliSecs(
194
+ firstPositive([
195
+ timing.domainLookupStart,
196
+ timing.connectStart,
197
+ timing.requestStart,
198
+ ])
199
+ ) || -1;
200
+ const dns =
201
+ timing.domainLookupEnd !== -1
202
+ ? roundMilliSecs(timing.domainLookupEnd - timing.domainLookupStart)
203
+ : -1;
204
+ const connect =
205
+ timing.connectEnd !== -1
206
+ ? roundMilliSecs(timing.connectEnd - timing.connectStart)
207
+ : -1;
208
+ const ssl =
209
+ timing.secureConnectionStart !== -1
210
+ ? roundMilliSecs(timing.connectEnd - timing.secureConnectionStart)
211
+ : -1;
212
+ const wait =
213
+ timing.responseStart !== -1
214
+ ? roundMilliSecs(timing.responseStart - timing.requestStart)
215
+ : -1;
178
216
 
179
- _onLoadingFailed(event: Protocol.Network.loadingFailedPayload) {
180
- const { requestId, timestamp } = event;
181
- this._requestCompleted(requestId, timestamp);
217
+ networkEntry.timings = {
218
+ blocked,
219
+ dns,
220
+ ssl,
221
+ connect,
222
+ send: 0, // not exposed via RT api
223
+ wait,
224
+ receive: -1, // will be available after full response is received
225
+ total: -1,
226
+ };
227
+ this._calcTotalTime(networkEntry, timing);
228
+
229
+ const page = request.frame().page();
230
+ this._addBarrier(
231
+ page,
232
+ request.allHeaders().then(reqHeaders => {
233
+ networkEntry.request.headers = reqHeaders;
234
+ networkEntry.request.referrer = reqHeaders?.referer;
235
+ })
236
+ );
237
+ this._addBarrier(
238
+ page,
239
+ response.allHeaders().then(resHeaders => {
240
+ networkEntry.response.headers = resHeaders;
241
+
242
+ const contentType = resHeaders['content-type'];
243
+ if (contentType)
244
+ networkEntry.response.mimeType = contentType.split(';')[0];
245
+ })
246
+ );
247
+ this._addBarrier(
248
+ page,
249
+ response.serverAddr().then(server => {
250
+ networkEntry.response.remoteIPAddress = server?.ipAddress;
251
+ networkEntry.response.remotePort = server?.port;
252
+ })
253
+ );
254
+ this._addBarrier(
255
+ page,
256
+ response.securityDetails().then(details => {
257
+ if (details) networkEntry.response.securityDetails = details;
258
+ })
259
+ );
182
260
  }
183
261
 
184
- _requestCompleted(
185
- requestId: string,
186
- endTime: number,
187
- encodedDataLength?: number
188
- ) {
189
- const record = this._findNetworkRecord(requestId);
190
- if (!record) {
262
+ private async _onRequestCompleted(request: Request) {
263
+ const networkEntry = this._findNetworkEntry(request);
264
+ if (!networkEntry) return;
265
+
266
+ networkEntry.loadEndTime = epochTimeInSeconds();
267
+ // responseEnd is fired after the last byte of the response is received.
268
+ const timing = request.timing();
269
+ const receive =
270
+ timing.responseEnd !== -1
271
+ ? roundMilliSecs(timing.responseEnd - timing.responseStart)
272
+ : -1;
273
+ networkEntry.timings.receive = receive;
274
+ this._calcTotalTime(networkEntry, timing);
275
+
276
+ // For aborted/failed requests sizes will not be present
277
+ if (timing.startTime <= 0) {
191
278
  return;
192
279
  }
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);
280
+
281
+ const page = request.frame().page();
282
+ this._addBarrier(
283
+ page,
284
+ request.sizes().then(sizes => {
285
+ networkEntry.request.bytes =
286
+ sizes.requestHeadersSize + sizes.requestBodySize;
287
+ networkEntry.request.body = {
288
+ bytes: sizes.requestBodySize,
289
+ };
290
+ const transferSize = sizes.responseHeadersSize + sizes.responseBodySize;
291
+ networkEntry.transferSize = transferSize;
292
+ networkEntry.response.bytes = transferSize;
293
+ networkEntry.response.body = {
294
+ bytes: sizes.responseBodySize,
295
+ };
296
+ })
297
+ );
204
298
  }
205
299
 
206
300
  /**
207
- * Account for missing response received event and also adjust the
208
- * response received event based on when first byte event was recorded
301
+ * Calculates the total time for the network request based on the ResourceTiming
302
+ * data from Playwright. Fallbacks to the event timings if ResourceTiming data
303
+ * is not available.
209
304
  */
210
- getResponseReceivedTime(
211
- timing: Protocol.Network.Response['timing'],
212
- responseReceivedTime: number
305
+ private _calcTotalTime(
306
+ entry: NetworkInfo,
307
+ rtiming: ReturnType<Request['timing']>
213
308
  ) {
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;
309
+ const timings = entry.timings;
310
+ entry.timings.total = [
311
+ timings.blocked,
312
+ timings.dns,
313
+ timings.connect,
314
+ timings.wait,
315
+ timings.receive,
316
+ ].reduce((pre, cur) => ((cur || -1) > 0 ? cur + pre : pre), 0);
317
+
318
+ // fallback when ResourceTiming data is not available
319
+ if (rtiming.startTime <= 0) {
320
+ const end =
321
+ entry.loadEndTime ||
322
+ entry.responseReceivedTime ||
323
+ entry.requestSentTime;
324
+ const total = roundMilliSecs((end - entry.requestSentTime) * 1000);
325
+ entry.timings.total = total <= 0 ? -1 : total;
227
326
  }
228
- return responseReceivedTime;
229
327
  }
230
328
 
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'] = {
237
- blocked: -1,
238
- queueing: -1,
239
- proxy: -1,
240
- dns: -1,
241
- ssl: -1,
242
- connect: -1,
243
- send: -1,
244
- wait: -1,
245
- receive: -1,
246
- total: -1,
247
- };
248
- const { requestSentTime, loadEndTime, responseReceivedTime } = record;
249
-
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
- const firstPositive = (numbers: number[]) => {
271
- for (let i = 0; i < numbers.length; ++i) {
272
- if (numbers[i] > 0) {
273
- return numbers[i];
274
- }
275
- }
276
- return null;
277
- };
329
+ async stop() {
278
330
  /**
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
331
+ * Waiting for all network events is error prone and might hang the tests
332
+ * from getting closed forever when there are upstream bugs in browsers or
333
+ * Playwright. So we log and drop these events once the test run is completed
281
334
  */
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
- }
294
- };
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
-
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);
326
- }
327
- const responseReceived = toMilliseconds(actResRcvdTime - startTime);
328
-
329
- if (response.fromServiceWorker) {
330
- addOffsetRange('blocked', 0, timing.workerStart);
331
- addOffsetRange('wait', timing.sendEnd, responseReceived);
332
- } else if (!timing.pushStart) {
333
- const blockingEnd =
334
- firstPositive([
335
- timing.dnsStart,
336
- 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
- );
335
+ if (this._barrierPromises.size > 0) {
336
+ log(`Plugins: dropping ${this._barrierPromises.size} network events`);
357
337
  }
358
- calculateDiffInMs('receive', actResRcvdTime, endTime);
359
- return result;
360
- }
361
-
362
- stop() {
338
+ const context = this.driver.context;
339
+ context.off('request', this._onRequest.bind(this));
340
+ context.off('response', this._onResponse.bind(this));
341
+ context.off('requestfinished', this._onRequestCompleted.bind(this));
342
+ context.off('requestfailed', this._onRequestCompleted.bind(this));
343
+ this._barrierPromises.clear();
344
+ log(`Plugins: stopped collecting network events`);
363
345
  return this.results;
364
346
  }
365
347
  }
@@ -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;