409 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			409 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google Inc. All Rights Reserved.
 | |
|  *
 | |
|  * Use of this source code is governed by an MIT-style license that can be
 | |
|  * found in the LICENSE file at https://angular.io/license
 | |
|  */
 | |
| 
 | |
| import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
 | |
| 
 | |
| import {ChromeDriverExtension, Options, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index';
 | |
| import {isBlank} from '../../src/facade/lang';
 | |
| import {TraceEventFactory} from '../trace_event_factory';
 | |
| 
 | |
| export function main() {
 | |
|   describe('chrome driver extension', () => {
 | |
|     var CHROME45_USER_AGENT =
 | |
|         '"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2499.0 Safari/537.36"';
 | |
| 
 | |
|     var log: any[];
 | |
|     var extension: ChromeDriverExtension;
 | |
| 
 | |
|     var blinkEvents = new TraceEventFactory('blink.console', 'pid0');
 | |
|     var v8Events = new TraceEventFactory('v8', 'pid0');
 | |
|     var v8EventsOtherProcess = new TraceEventFactory('v8', 'pid1');
 | |
|     var chromeTimelineEvents =
 | |
|         new TraceEventFactory('disabled-by-default-devtools.timeline', 'pid0');
 | |
|     var chrome45TimelineEvents = new TraceEventFactory('devtools.timeline', 'pid0');
 | |
|     var chromeTimelineV8Events = new TraceEventFactory('devtools.timeline,v8', 'pid0');
 | |
|     var chromeBlinkTimelineEvents = new TraceEventFactory('blink,devtools.timeline', 'pid0');
 | |
|     var chromeBlinkUserTimingEvents = new TraceEventFactory('blink.user_timing', 'pid0');
 | |
|     var benchmarkEvents = new TraceEventFactory('benchmark', 'pid0');
 | |
|     var normEvents = new TraceEventFactory('timeline', 'pid0');
 | |
| 
 | |
|     function createExtension(
 | |
|         perfRecords: any[] = null, userAgent: string = null,
 | |
|         messageMethod = 'Tracing.dataCollected'): WebDriverExtension {
 | |
|       if (!perfRecords) {
 | |
|         perfRecords = [];
 | |
|       }
 | |
|       if (isBlank(userAgent)) {
 | |
|         userAgent = CHROME45_USER_AGENT;
 | |
|       }
 | |
|       log = [];
 | |
|       extension = ReflectiveInjector
 | |
|                       .resolveAndCreate([
 | |
|                         ChromeDriverExtension.PROVIDERS, {
 | |
|                           provide: WebDriverAdapter,
 | |
|                           useValue: new MockDriverAdapter(log, perfRecords, messageMethod)
 | |
|                         },
 | |
|                         {provide: Options.USER_AGENT, useValue: userAgent}
 | |
|                       ])
 | |
|                       .get(ChromeDriverExtension);
 | |
|       return extension;
 | |
|     }
 | |
| 
 | |
|     it('should force gc via window.gc()',
 | |
|        inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension().gc().then((_) => {
 | |
|            expect(log).toEqual([['executeScript', 'window.gc()']]);
 | |
|            async.done();
 | |
|          });
 | |
|        }));
 | |
| 
 | |
|     it('should mark the timeline via console.time()',
 | |
|        inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension().timeBegin('someName').then((_) => {
 | |
|            expect(log).toEqual([['executeScript', `console.time('someName');`]]);
 | |
|            async.done();
 | |
|          });
 | |
|        }));
 | |
| 
 | |
|     it('should mark the timeline via console.timeEnd()',
 | |
|        inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension().timeEnd('someName', null).then((_) => {
 | |
|            expect(log).toEqual([['executeScript', `console.timeEnd('someName');`]]);
 | |
|            async.done();
 | |
|          });
 | |
|        }));
 | |
| 
 | |
|     it('should mark the timeline via console.time() and console.timeEnd()',
 | |
|        inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension().timeEnd('name1', 'name2').then((_) => {
 | |
|            expect(log).toEqual(
 | |
|                [['executeScript', `console.timeEnd('name1');console.time('name2');`]]);
 | |
|            async.done();
 | |
|          });
 | |
|        }));
 | |
| 
 | |
|     it('should normalize times to ms and forward ph and pid event properties',
 | |
|        inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension([chromeTimelineV8Events.complete('FunctionCall', 1100, 5500, null)])
 | |
|              .readPerfLog()
 | |
|              .then((events) => {
 | |
|                expect(events).toEqual([
 | |
|                  normEvents.complete('script', 1.1, 5.5, null),
 | |
|                ]);
 | |
|                async.done();
 | |
|              });
 | |
|        }));
 | |
| 
 | |
|     it('should normalize "tdur" to "dur"',
 | |
|        inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          var event: any = chromeTimelineV8Events.create('X', 'FunctionCall', 1100, null);
 | |
|          event['tdur'] = 5500;
 | |
|          createExtension([event]).readPerfLog().then((events) => {
 | |
|            expect(events).toEqual([
 | |
|              normEvents.complete('script', 1.1, 5.5, null),
 | |
|            ]);
 | |
|            async.done();
 | |
|          });
 | |
|        }));
 | |
| 
 | |
|     it('should report FunctionCall events as "script"',
 | |
|        inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension([chromeTimelineV8Events.start('FunctionCall', 0)])
 | |
|              .readPerfLog()
 | |
|              .then((events) => {
 | |
|                expect(events).toEqual([
 | |
|                  normEvents.start('script', 0),
 | |
|                ]);
 | |
|                async.done();
 | |
|              });
 | |
|        }));
 | |
| 
 | |
|     it('should report EvaluateScript events as "script"',
 | |
|        inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension([chromeTimelineV8Events.start('EvaluateScript', 0)])
 | |
|              .readPerfLog()
 | |
|              .then((events) => {
 | |
|                expect(events).toEqual([
 | |
|                  normEvents.start('script', 0),
 | |
|                ]);
 | |
|                async.done();
 | |
|              });
 | |
|        }));
 | |
| 
 | |
|     it('should report minor gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension([
 | |
|            chromeTimelineV8Events.start('MinorGC', 1000, {'usedHeapSizeBefore': 1000}),
 | |
|            chromeTimelineV8Events.end('MinorGC', 2000, {'usedHeapSizeAfter': 0}),
 | |
|          ])
 | |
|              .readPerfLog()
 | |
|              .then((events) => {
 | |
|                expect(events.length).toEqual(2);
 | |
|                expect(events[0]).toEqual(
 | |
|                    normEvents.start('gc', 1.0, {'usedHeapSize': 1000, 'majorGc': false}));
 | |
|                expect(events[1]).toEqual(
 | |
|                    normEvents.end('gc', 2.0, {'usedHeapSize': 0, 'majorGc': false}));
 | |
|                async.done();
 | |
|              });
 | |
|        }));
 | |
| 
 | |
|     it('should report major gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension(
 | |
|              [
 | |
|                chromeTimelineV8Events.start('MajorGC', 1000, {'usedHeapSizeBefore': 1000}),
 | |
|                chromeTimelineV8Events.end('MajorGC', 2000, {'usedHeapSizeAfter': 0}),
 | |
|              ], )
 | |
|              .readPerfLog()
 | |
|              .then((events) => {
 | |
|                expect(events.length).toEqual(2);
 | |
|                expect(events[0]).toEqual(
 | |
|                    normEvents.start('gc', 1.0, {'usedHeapSize': 1000, 'majorGc': true}));
 | |
|                expect(events[1]).toEqual(
 | |
|                    normEvents.end('gc', 2.0, {'usedHeapSize': 0, 'majorGc': true}));
 | |
|                async.done();
 | |
|              });
 | |
|        }));
 | |
| 
 | |
|     ['Layout', 'UpdateLayerTree', 'Paint'].forEach((recordType) => {
 | |
|       it(`should report ${recordType} as "render"`,
 | |
|          inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|            createExtension(
 | |
|                [
 | |
|                  chrome45TimelineEvents.start(recordType, 1234),
 | |
|                  chrome45TimelineEvents.end(recordType, 2345)
 | |
|                ], )
 | |
|                .readPerfLog()
 | |
|                .then((events) => {
 | |
|                  expect(events).toEqual([
 | |
|                    normEvents.start('render', 1.234),
 | |
|                    normEvents.end('render', 2.345),
 | |
|                  ]);
 | |
|                  async.done();
 | |
|                });
 | |
|          }));
 | |
|     });
 | |
| 
 | |
|     it(`should report UpdateLayoutTree as "render"`,
 | |
|        inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension(
 | |
|              [
 | |
|                chromeBlinkTimelineEvents.start('UpdateLayoutTree', 1234),
 | |
|                chromeBlinkTimelineEvents.end('UpdateLayoutTree', 2345)
 | |
|              ], )
 | |
|              .readPerfLog()
 | |
|              .then((events) => {
 | |
|                expect(events).toEqual([
 | |
|                  normEvents.start('render', 1.234),
 | |
|                  normEvents.end('render', 2.345),
 | |
|                ]);
 | |
|                async.done();
 | |
|              });
 | |
|        }));
 | |
| 
 | |
|     it('should ignore FunctionCalls from webdriver',
 | |
|        inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension([chromeTimelineV8Events.start(
 | |
|                              'FunctionCall', 0, {'data': {'scriptName': 'InjectedScript'}})])
 | |
|              .readPerfLog()
 | |
|              .then((events) => {
 | |
|                expect(events).toEqual([]);
 | |
|                async.done();
 | |
|              });
 | |
|        }));
 | |
| 
 | |
|     it('should ignore FunctionCalls with empty scriptName',
 | |
|        inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension(
 | |
|              [chromeTimelineV8Events.start('FunctionCall', 0, {'data': {'scriptName': ''}})])
 | |
|              .readPerfLog()
 | |
|              .then((events) => {
 | |
|                expect(events).toEqual([]);
 | |
|                async.done();
 | |
|              });
 | |
|        }));
 | |
| 
 | |
|     it('should report navigationStart',
 | |
|        inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension([chromeBlinkUserTimingEvents.instant('navigationStart', 1234)])
 | |
|              .readPerfLog()
 | |
|              .then((events) => {
 | |
|                expect(events).toEqual([normEvents.instant('navigationStart', 1.234)]);
 | |
|                async.done();
 | |
|              });
 | |
|        }));
 | |
| 
 | |
|     it('should report receivedData', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension([chrome45TimelineEvents.instant(
 | |
|                              'ResourceReceivedData', 1234, {'data': {'encodedDataLength': 987}})], )
 | |
|              .readPerfLog()
 | |
|              .then((events) => {
 | |
|                expect(events).toEqual(
 | |
|                    [normEvents.instant('receivedData', 1.234, {'encodedDataLength': 987})]);
 | |
|                async.done();
 | |
|              });
 | |
|        }));
 | |
| 
 | |
|     it('should report sendRequest', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|          createExtension([chrome45TimelineEvents.instant(
 | |
|                              'ResourceSendRequest', 1234,
 | |
|                              {'data': {'url': 'http://here', 'requestMethod': 'GET'}})], )
 | |
|              .readPerfLog()
 | |
|              .then((events) => {
 | |
|                expect(events).toEqual([normEvents.instant(
 | |
|                    'sendRequest', 1.234, {'url': 'http://here', 'method': 'GET'})]);
 | |
|                async.done();
 | |
|              });
 | |
|        }));
 | |
| 
 | |
|     describe('readPerfLog (common)', () => {
 | |
| 
 | |
|       it('should execute a dummy script before reading them',
 | |
|          inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|            // TODO(tbosch): This seems to be a bug in ChromeDriver:
 | |
|            // Sometimes it does not report the newest events of the performance log
 | |
|            // to the WebDriver client unless a script is executed...
 | |
|            createExtension([]).readPerfLog().then((_) => {
 | |
|              expect(log).toEqual([['executeScript', '1+1'], ['logs', 'performance']]);
 | |
|              async.done();
 | |
|            });
 | |
|          }));
 | |
| 
 | |
|       ['Rasterize', 'CompositeLayers'].forEach((recordType) => {
 | |
|         it(`should report ${recordType} as "render"`,
 | |
|            inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|              createExtension(
 | |
|                  [
 | |
|                    chromeTimelineEvents.start(recordType, 1234),
 | |
|                    chromeTimelineEvents.end(recordType, 2345)
 | |
|                  ], )
 | |
|                  .readPerfLog()
 | |
|                  .then((events) => {
 | |
|                    expect(events).toEqual([
 | |
|                      normEvents.start('render', 1.234),
 | |
|                      normEvents.end('render', 2.345),
 | |
|                    ]);
 | |
|                    async.done();
 | |
|                  });
 | |
|            }));
 | |
|       });
 | |
| 
 | |
|       describe('frame metrics', () => {
 | |
|         it('should report ImplThreadRenderingStats as frame event',
 | |
|            inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|              createExtension([benchmarkEvents.instant(
 | |
|                                  'BenchmarkInstrumentation::ImplThreadRenderingStats', 1100,
 | |
|                                  {'data': {'frame_count': 1}})])
 | |
|                  .readPerfLog()
 | |
|                  .then((events) => {
 | |
|                    expect(events).toEqual([
 | |
|                      normEvents.instant('frame', 1.1),
 | |
|                    ]);
 | |
|                    async.done();
 | |
|                  });
 | |
|            }));
 | |
| 
 | |
|         it('should not report ImplThreadRenderingStats with zero frames',
 | |
|            inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|              createExtension([benchmarkEvents.instant(
 | |
|                                  'BenchmarkInstrumentation::ImplThreadRenderingStats', 1100,
 | |
|                                  {'data': {'frame_count': 0}})])
 | |
|                  .readPerfLog()
 | |
|                  .then((events) => {
 | |
|                    expect(events).toEqual([]);
 | |
|                    async.done();
 | |
|                  });
 | |
|            }));
 | |
| 
 | |
|         it('should throw when ImplThreadRenderingStats contains more than one frame',
 | |
|            inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
| 
 | |
|              createExtension([benchmarkEvents.instant(
 | |
|                                  'BenchmarkInstrumentation::ImplThreadRenderingStats', 1100,
 | |
|                                  {'data': {'frame_count': 2}})])
 | |
|                  .readPerfLog()
 | |
|                  .catch((err): any => {
 | |
|                    expect(() => {
 | |
|                      throw err;
 | |
|                    }).toThrowError('multi-frame render stats not supported');
 | |
|                    async.done();
 | |
|                  });
 | |
|            }));
 | |
| 
 | |
|       });
 | |
| 
 | |
|       it('should report begin timestamps',
 | |
|          inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|            createExtension([blinkEvents.create('S', 'someName', 1000)])
 | |
|                .readPerfLog()
 | |
|                .then((events) => {
 | |
|                  expect(events).toEqual([normEvents.markStart('someName', 1.0)]);
 | |
|                  async.done();
 | |
|                });
 | |
|          }));
 | |
| 
 | |
|       it('should report end timestamps',
 | |
|          inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
|            createExtension([blinkEvents.create('F', 'someName', 1000)])
 | |
|                .readPerfLog()
 | |
|                .then((events) => {
 | |
|                  expect(events).toEqual([normEvents.markEnd('someName', 1.0)]);
 | |
|                  async.done();
 | |
|                });
 | |
|          }));
 | |
| 
 | |
|       it('should throw an error on buffer overflow',
 | |
|          inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
 | |
| 
 | |
|            createExtension(
 | |
|                [
 | |
|                  chromeTimelineEvents.start('FunctionCall', 1234),
 | |
|                ],
 | |
|                CHROME45_USER_AGENT, 'Tracing.bufferUsage')
 | |
|                .readPerfLog()
 | |
|                .catch((err): any => {
 | |
|                  expect(() => {
 | |
|                    throw err;
 | |
|                  }).toThrowError('The DevTools trace buffer filled during the test!');
 | |
|                  async.done();
 | |
|                });
 | |
|          }));
 | |
| 
 | |
|       it('should match chrome browsers', () => {
 | |
|         expect(createExtension().supports({'browserName': 'chrome'})).toBe(true);
 | |
| 
 | |
|         expect(createExtension().supports({'browserName': 'Chrome'})).toBe(true);
 | |
|       });
 | |
| 
 | |
|     });
 | |
| 
 | |
|   });
 | |
| }
 | |
| 
 | |
| class MockDriverAdapter extends WebDriverAdapter {
 | |
|   constructor(private _log: any[], private _events: any[], private _messageMethod: string) {
 | |
|     super();
 | |
|   }
 | |
| 
 | |
|   executeScript(script: string) {
 | |
|     this._log.push(['executeScript', script]);
 | |
|     return Promise.resolve(null);
 | |
|   }
 | |
| 
 | |
|   logs(type: string) {
 | |
|     this._log.push(['logs', type]);
 | |
|     if (type === 'performance') {
 | |
|       return Promise.resolve(this._events.map(
 | |
|           (event) => ({
 | |
|             'message': JSON.stringify(
 | |
|                 {'message': {'method': this._messageMethod, 'params': event}}, null, 2)
 | |
|           })));
 | |
|     } else {
 | |
|       return null;
 | |
|     }
 | |
|   }
 | |
| }
 |