f17b70e3e683fdcbf2671efa83bb3f6de0ec9a2e
[WebKit.git] / Websites / perf.webkit.org / browser-tests / index.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset="utf-8">
5 <title>In-Browser Tests for Performance Dashboard</title>
6 <link rel="stylesheet" href="../node_modules/mocha/mocha.css">
7 <script src="../node_modules/mocha/mocha.js"></script>
8 <script src="https://cdnjs.cloudflare.com/ajax/libs/expect.js/0.2.0/expect.min.js"></script>
9 <script>
10
11 mocha.setup('bdd');
12
13 </script>
14 <script src="../unit-tests/resources/mock-remote-api.js"></script>
15 </head>
16 <body>
17 <div id="mocha"></div>
18 <script src="component-base-tests.js"></script>
19 <script src="close-button-tests.js"></script>
20 <script src="editable-text-tests.js"></script>
21 <script src="time-series-chart-tests.js"></script>
22 <script src="interactive-time-series-chart-tests.js"></script>
23 <script src="chart-status-evaluator-tests.js"></script>
24 <script src="chart-revision-range-tests.js"></script>
25 <script src="commit-log-viewer-tests.js"></script>
26 <script>
27
28 afterEach(() => {
29     BrowsingContext.cleanup();
30 });
31
32 class BrowsingContext {
33
34     constructor()
35     {
36         let iframe = document.createElement('iframe');
37         document.body.appendChild(iframe);
38         iframe.style.position = 'absolute';
39         iframe.style.left = '0px';
40         iframe.style.top = '0px';
41         BrowsingContext._iframes.push(iframe);
42
43         // Expedite calls to callbacks to make tests go faster.
44         iframe.contentWindow.requestAnimationFrame = (callback) => setTimeout(callback, 0);
45
46         this.iframe = iframe;
47         this.symbols = {};
48         this.global = this.iframe.contentWindow;
49         this.document = this.iframe.contentDocument;
50         this._didLoadMockRemote = false;
51     }
52
53     importScripts(pathList, ...symbolList)
54     {
55         const doc = this.iframe.contentDocument;
56         const global = this.iframe.contentWindow;
57
58         pathList = pathList.map((path) => `../public/v3/${path}`);
59         if (!this._didLoadMockRemote) {
60             this._didLoadMockRemote = true;
61             pathList.unshift('../unit-tests/resources/mock-remote-api.js');
62         }
63
64         return Promise.all(pathList.map((path) => {
65             return new Promise((resolve, reject) => {
66                 let script = doc.createElement('script');
67                 script.addEventListener('load', resolve);
68                 script.addEventListener('error', reject);
69                 script.src = path;
70                 script.async = false;
71                 doc.body.appendChild(script);
72             });
73         })).then(() => {
74             const script = doc.createElement('script');
75             script.textContent = `window.importedSymbols = [${symbolList.join(', ')}];`;
76             global.RemoteAPI = global.MockRemoteAPI;
77             doc.body.appendChild(script);
78
79             const importedSymbols = global.importedSymbols;
80             for (let i = 0; i < symbolList.length; i++)
81                 this.symbols[symbolList[i]] = importedSymbols[i];
82
83             return symbolList.length == 1 ? importedSymbols[0] : importedSymbols;
84         });
85     }
86
87     importScript(path, ...symbols)
88     {
89         return this.importScripts([path], ...symbols);
90     }
91
92     static cleanup()
93     {
94         BrowsingContext._iframes.forEach((iframe) => { iframe.remove(); });
95         BrowsingContext._iframes = [];
96     }
97 }
98 BrowsingContext._iframes = [];
99
100 function waitForComponentsToRender(context)
101 {
102     if (!context._dummyComponent) {
103         const ComponentBase = context.symbols.ComponentBase;
104         context._dummyComponent = class SomeComponent extends ComponentBase {
105             constructor(resolve)
106             {
107                 super();
108                 this._resolve = resolve;
109             }
110             render() { setTimeout(this._resolve, 0); }
111         }
112         ComponentBase.defineElement('dummy-component', context._dummyComponent);
113     }
114     return new Promise((resolve) => {
115         const instance = new context._dummyComponent(resolve);
116         context.document.body.appendChild(instance.element());
117         setTimeout(() => {
118             instance.enqueueToRender();
119         }, 0);
120     });
121 }
122
123 function wait(milliseconds)
124 {
125     return new Promise((resolve) => {
126         setTimeout(resolve, milliseconds);
127     });
128 }
129
130 function canvasImageData(canvas)
131 {
132     return canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
133 }
134
135 function canvasRefTest(canvas1, canvas2, shouldMatch)
136 {
137     expect(canvas1.offsetWidth).to.be(canvas2.offsetWidth);
138     expect(canvas2.offsetHeight).to.be(canvas2.offsetHeight);
139     const data1 = canvasImageData(canvas1).data;
140     const data2 = canvasImageData(canvas2).data;
141     expect(data1.length).to.be.a('number');
142     expect(data1.length).to.be(data2.length);
143
144     let match = true;
145     for (let i = 0; i < data1.length; i++) {
146         if (data1[i] != data2[i]) {
147             match = false;
148             break;
149         }
150     }
151
152     if (match == shouldMatch)
153         return;
154
155     [canvas1, canvas2].forEach((canvas) => {
156         let image = document.createElement('img');
157         image.src = canvas.toDataURL();
158         image.style.display = 'block';
159         document.body.appendChild(image);
160     });
161
162     throw new Error(shouldMatch ? 'Canvas contents were different' : 'Canvas contents were identical');
163 }
164
165 const CanvasTest = {
166     fillCanvasBeforeRedrawCheck(canvas)
167     {
168         const canvasContext = canvas.getContext('2d');
169         canvasContext.fillStyle = 'white';
170         canvasContext.fillRect(0, 0, canvas.width, canvas.height);
171     },
172
173     hasCanvasBeenRedrawn(canvas)
174     {
175         return canvasImageData(canvas).data.some((value) => value != 255);
176     },
177
178     canvasImageData(canvas) { return canvasImageData(canvas); },
179
180     canvasContainsColor(canvas, color, rect = {})
181     {
182         const content = canvas.getContext('2d').getImageData(rect.x || 0, rect.y || 0, rect.width || canvas.width, rect.height || canvas.height);
183         let found = false;
184         const data = content.data;
185         for (let startOfPixel = 0; startOfPixel < data.length; startOfPixel += 4) {
186             let r = data[startOfPixel];
187             let g = data[startOfPixel + 1];
188             let b = data[startOfPixel + 2];
189             let a = data[startOfPixel + 3];
190             if (r == color.r && g == color.g && b == color.b && (color.a == undefined || a == color.a))
191                 return true;
192         }
193         return false;
194     },
195
196     expectCanvasesMatch(canvas1, canvas2) { return canvasRefTest(canvas1, canvas2, true); },
197     expectCanvasesMismatch(canvas1, canvas2) { return canvasRefTest(canvas1, canvas2, false); },
198 }
199
200 const dayInMilliseconds = 24 * 3600 * 1000;
201
202 function posixTime(string) { return +new Date(string); }
203
204 const ChartTest = {
205     importChartScripts(context)
206     {
207         return context.importScripts([
208             '../shared/statistics.js',
209             'instrumentation.js',
210             'models/data-model.js',
211             'models/time-series.js',
212             'models/measurement-set.js',
213             'models/measurement-cluster.js',
214             'models/measurement-adaptor.js',
215             'models/repository.js',
216             'models/platform.js',
217             'models/test.js',
218             'models/metric.js',
219             'models/commit-set.js',
220             'models/commit-log.js',
221             'components/base.js',
222             'components/time-series-chart.js',
223             'components/interactive-time-series-chart.js'],
224             'ComponentBase', 'TimeSeriesChart', 'InteractiveTimeSeriesChart',
225             'Platform', 'Metric', 'Test', 'Repository', 'MeasurementSet', 'MockRemoteAPI').then(() => {
226                 return context.symbols.TimeSeriesChart;
227             })
228     },
229
230     posixTime: posixTime,
231
232     get sampleCluster() { return this.makeSampleCluster(); },
233
234     makeModelObjectsForSampleCluster(context)
235     {
236         const test = context.symbols.Test.ensureSingleton(2, {name: 'Test'});
237         const metric = context.symbols.Metric.ensureSingleton(1, {name: 'Time', test})
238         const platform = context.symbols.Platform.ensureSingleton(1,
239             {name: 'SomePlatform', metrics: [metric], lastModifiedByMetric: [posixTime('2016-01-18T00:00:00Z')]});
240         metric.addPlatform(platform);
241         context.symbols.Repository.ensureSingleton(1, {name: 'SomeApp'});
242         context.symbols.Repository.ensureSingleton(2, {name: 'macOS'});
243     },
244
245     makeSampleCluster(options = {})
246     {
247         const baselineStart = options.baselineIsSmaller ? 30 : 130;
248         const targetStart = options.targetIsBigger ? 120 : 90;
249         return {
250         "clusterStart": posixTime('2016-01-01T00:00:00Z'),
251         "clusterSize": 7 * dayInMilliseconds,
252         "startTime": posixTime('2016-01-01T00:00:00Z'),
253         "endTime": posixTime('2016-01-08T00:00:00Z'),
254         "lastModified": posixTime('2016-01-18T00:00:00Z'),
255         "clusterCount": 1,
256         "status": "OK",
257         "formatMap": [
258             "id", "mean", "iterationCount", "sum", "squareSum", "markedOutlier",
259             "revisions",
260             "commitTime", "build", "buildTime", "buildNumber", "builder"
261         ],
262         "configurations": {
263             "current": [
264                 [
265                     1000, 100, 1, 100, 100 * 100, false,
266                     [ [2000, 1, "4000", posixTime('2016-01-05T17:35:00Z')], [3000, 2, "15B42", 0] ],
267                     posixTime('2016-01-05T17:35:00Z'), 5000, posixTime('2016-01-05T19:23:00Z'), "10", 7
268                 ],
269                 [
270                     1001, 131, 1, 131, 131 * 131, true,
271                     [ [2001, 1, "4001", posixTime('2016-01-05T18:43:01Z')], [3000, 2, "15B42", 0] ],
272                     posixTime('2016-01-05T18:43:01Z'), 5001, posixTime('2016-01-05T20:58:01Z'), "11", 7
273                 ],
274                 [
275                     1002, 122, 1, 122, 122 * 122, false,
276                     [ [2002, 1, "4002", posixTime('2016-01-05T20:01:02Z')], [3000, 2, "15B42", 0] ],
277                     posixTime('2016-01-05T20:01:02Z'), 5002, posixTime('2016-01-05T22:37:02Z'), "12", 7
278                 ],
279                 [
280                     1003, 113, 1, 113, 113 * 113, false,
281                     [ [2003, 1, "4003", posixTime('2016-01-05T23:19:03Z')], [3000, 2, "15B42", 0] ],
282                     posixTime('2016-01-05T23:19:03Z'), 5003, posixTime('2016-01-06T23:19:03Z'), "13", 7
283                 ],
284                 [
285                     1004, 124, 1, 124, 124 * 124, false,
286                     [ [2004, 1, "4004", posixTime('2016-01-06T01:52:04Z')], [3001, 2, "15C50", 0] ],
287                     posixTime('2016-01-06T01:52:04Z'), 5004, posixTime('2016-01-06T02:42:04Z'), "14", 7
288                 ],
289                 [
290                     1005, 115, 1, 115, 115 * 115, true,
291                     [ [2005, 1, "4005", posixTime('2016-01-06T03:22:05Z')], [3001, 2, "15C50", 0] ],
292                     posixTime('2016-01-06T03:22:05Z'), 5005, posixTime('2016-01-06T06:01:05Z'), "15", 7
293                 ],
294                 [
295                     1006, 116, 1, 116, 116 * 116, false,
296                     [ [2006, 1, "4006", posixTime('2016-01-06T05:59:06Z')], [3001, 2, "15C50", 0] ],
297                     posixTime('2016-01-06T05:59:06Z'), 5006, posixTime('2016-01-06T08:34:06Z'), "16", 7
298                 ]
299             ],
300             "baseline": [
301                 [
302                     7000, baselineStart, 1, baselineStart, baselineStart * baselineStart, false,
303                     [ ],
304                     posixTime('2016-01-05T12:00:30Z'), 5030, posixTime('2016-01-05T12:00:30Z'), "30", 7
305                 ],
306                 [
307                     7001, baselineStart + 1, 1, baselineStart + 1, Math.pow(baselineStart + 1, 2), false,
308                     [ ],
309                     posixTime('2016-01-06T00:00:31Z'), 5031, posixTime('2016-01-06T00:00:31Z'), "31", 7
310                 ],
311             ],
312             "target": [
313                 [
314                     8000, targetStart, 1, targetStart, targetStart * targetStart, false,
315                     [ ],
316                     posixTime('2016-01-05T12:00:30Z'), 5030, posixTime('2016-01-05T12:00:30Z'), "90", 7
317                 ],
318                 [
319                     8001, targetStart + 1, 1, targetStart + 1, Math.pow(targetStart + 1, 2), false,
320                     [ ],
321                     posixTime('2016-01-06T00:00:31Z'), 5031, posixTime('2016-01-06T00:00:31Z'), "91", 7
322                 ],
323             ]
324         }};
325     },
326
327     createChartWithSampleCluster(context, sourceList = null, chartOptions = {}, className = 'TimeSeriesChart')
328     {
329         const TimeSeriesChart = context.symbols[className];
330         const MeasurementSet = context.symbols.MeasurementSet;
331
332         if (sourceList == null)
333             sourceList = [{type: 'current'}];
334
335         const sampleCluster = MeasurementSet.findSet(1, 1, 0);
336         for (let source of sourceList) {
337             if (!source.type)
338                 source.type = 'current';
339             source.measurementSet = sampleCluster;
340         }
341
342         const chart = new TimeSeriesChart(sourceList, chartOptions);
343         const element = chart.element();
344         element.style.width = chartOptions.width || '300px';
345         element.style.height = chartOptions.height || '100px';
346         context.document.body.appendChild(element);
347
348         return chart;
349     },
350
351     createInteractiveChartWithSampleCluster(context, sourceList = null, chartOptions = {})
352     {
353         if (sourceList == null)
354             sourceList = [{type: 'current', interactive: true}];
355         return this.createChartWithSampleCluster(context, sourceList, chartOptions, 'InteractiveTimeSeriesChart');
356     },
357
358     respondWithSampleCluster(request, options)
359     {
360         expect(request.url).to.be('../data/measurement-set-1-1.json');
361         expect(request.method).to.be('GET');
362         request.resolve(this.makeSampleCluster(options));
363     },
364 };
365
366 mocha.checkLeaks();
367 mocha.globals(['expect', 'BrowsingContext', 'CanvasTest', 'ChartTest', 'wait', 'waitForComponentsToRender']);
368 mocha.run();
369
370 </script>
371 </body>
372 </html>