1/**
2 * Copyright 2018 Google Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import expect from 'expect';
18import {
19  getTestState,
20  setupTestPageAndContextHooks,
21  setupTestBrowserHooks,
22  describeChromeOnly,
23} from './mocha-utils'; // eslint-disable-line import/extensions
24
25describe('Coverage specs', function () {
26  describeChromeOnly('JSCoverage', function () {
27    setupTestBrowserHooks();
28    setupTestPageAndContextHooks();
29
30    it('should work', async () => {
31      const { page, server } = getTestState();
32      await page.coverage.startJSCoverage();
33      await page.goto(server.PREFIX + '/jscoverage/simple.html', {
34        waitUntil: 'networkidle0',
35      });
36      const coverage = await page.coverage.stopJSCoverage();
37      expect(coverage.length).toBe(1);
38      expect(coverage[0].url).toContain('/jscoverage/simple.html');
39      expect(coverage[0].ranges).toEqual([
40        { start: 0, end: 17 },
41        { start: 35, end: 61 },
42      ]);
43    });
44    it('should report sourceURLs', async () => {
45      const { page, server } = getTestState();
46
47      await page.coverage.startJSCoverage();
48      await page.goto(server.PREFIX + '/jscoverage/sourceurl.html');
49      const coverage = await page.coverage.stopJSCoverage();
50      expect(coverage.length).toBe(1);
51      expect(coverage[0].url).toBe('nicename.js');
52    });
53    it('should ignore eval() scripts by default', async () => {
54      const { page, server } = getTestState();
55
56      await page.coverage.startJSCoverage();
57      await page.goto(server.PREFIX + '/jscoverage/eval.html');
58      const coverage = await page.coverage.stopJSCoverage();
59      expect(coverage.length).toBe(1);
60    });
61    it("shouldn't ignore eval() scripts if reportAnonymousScripts is true", async () => {
62      const { page, server } = getTestState();
63
64      await page.coverage.startJSCoverage({ reportAnonymousScripts: true });
65      await page.goto(server.PREFIX + '/jscoverage/eval.html');
66      const coverage = await page.coverage.stopJSCoverage();
67      expect(
68        coverage.find((entry) => entry.url.startsWith('debugger://'))
69      ).not.toBe(null);
70      expect(coverage.length).toBe(2);
71    });
72    it('should ignore pptr internal scripts if reportAnonymousScripts is true', async () => {
73      const { page, server } = getTestState();
74
75      await page.coverage.startJSCoverage({ reportAnonymousScripts: true });
76      await page.goto(server.EMPTY_PAGE);
77      await page.evaluate('console.log("foo")');
78      await page.evaluate(() => console.log('bar'));
79      const coverage = await page.coverage.stopJSCoverage();
80      expect(coverage.length).toBe(0);
81    });
82    it('should report multiple scripts', async () => {
83      const { page, server } = getTestState();
84
85      await page.coverage.startJSCoverage();
86      await page.goto(server.PREFIX + '/jscoverage/multiple.html');
87      const coverage = await page.coverage.stopJSCoverage();
88      expect(coverage.length).toBe(2);
89      coverage.sort((a, b) => a.url.localeCompare(b.url));
90      expect(coverage[0].url).toContain('/jscoverage/script1.js');
91      expect(coverage[1].url).toContain('/jscoverage/script2.js');
92    });
93    it('should report right ranges', async () => {
94      const { page, server } = getTestState();
95
96      await page.coverage.startJSCoverage();
97      await page.goto(server.PREFIX + '/jscoverage/ranges.html');
98      const coverage = await page.coverage.stopJSCoverage();
99      expect(coverage.length).toBe(1);
100      const entry = coverage[0];
101      expect(entry.ranges.length).toBe(1);
102      const range = entry.ranges[0];
103      expect(entry.text.substring(range.start, range.end)).toBe(
104        `console.log('used!');`
105      );
106    });
107    it('should report scripts that have no coverage', async () => {
108      const { page, server } = getTestState();
109
110      await page.coverage.startJSCoverage();
111      await page.goto(server.PREFIX + '/jscoverage/unused.html');
112      const coverage = await page.coverage.stopJSCoverage();
113      expect(coverage.length).toBe(1);
114      const entry = coverage[0];
115      expect(entry.url).toContain('unused.html');
116      expect(entry.ranges.length).toBe(0);
117    });
118    it('should work with conditionals', async () => {
119      const { page, server } = getTestState();
120
121      await page.coverage.startJSCoverage();
122      await page.goto(server.PREFIX + '/jscoverage/involved.html');
123      const coverage = await page.coverage.stopJSCoverage();
124      expect(
125        JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':<PORT>/')
126      ).toBeGolden('jscoverage-involved.txt');
127    });
128    // @see https://crbug.com/990945
129    xit('should not hang when there is a debugger statement', async () => {
130      const { page, server } = getTestState();
131
132      await page.coverage.startJSCoverage();
133      await page.goto(server.EMPTY_PAGE);
134      await page.evaluate(() => {
135        debugger; // eslint-disable-line no-debugger
136      });
137      await page.coverage.stopJSCoverage();
138    });
139    describe('resetOnNavigation', function () {
140      it('should report scripts across navigations when disabled', async () => {
141        const { page, server } = getTestState();
142
143        await page.coverage.startJSCoverage({ resetOnNavigation: false });
144        await page.goto(server.PREFIX + '/jscoverage/multiple.html');
145        await page.goto(server.EMPTY_PAGE);
146        const coverage = await page.coverage.stopJSCoverage();
147        expect(coverage.length).toBe(2);
148      });
149
150      it('should NOT report scripts across navigations when enabled', async () => {
151        const { page, server } = getTestState();
152
153        await page.coverage.startJSCoverage(); // Enabled by default.
154        await page.goto(server.PREFIX + '/jscoverage/multiple.html');
155        await page.goto(server.EMPTY_PAGE);
156        const coverage = await page.coverage.stopJSCoverage();
157        expect(coverage.length).toBe(0);
158      });
159    });
160  });
161
162  describeChromeOnly('CSSCoverage', function () {
163    setupTestBrowserHooks();
164    setupTestPageAndContextHooks();
165
166    it('should work', async () => {
167      const { page, server } = getTestState();
168
169      await page.coverage.startCSSCoverage();
170      await page.goto(server.PREFIX + '/csscoverage/simple.html');
171      const coverage = await page.coverage.stopCSSCoverage();
172      expect(coverage.length).toBe(1);
173      expect(coverage[0].url).toContain('/csscoverage/simple.html');
174      expect(coverage[0].ranges).toEqual([{ start: 1, end: 22 }]);
175      const range = coverage[0].ranges[0];
176      expect(coverage[0].text.substring(range.start, range.end)).toBe(
177        'div { color: green; }'
178      );
179    });
180    it('should report sourceURLs', async () => {
181      const { page, server } = getTestState();
182
183      await page.coverage.startCSSCoverage();
184      await page.goto(server.PREFIX + '/csscoverage/sourceurl.html');
185      const coverage = await page.coverage.stopCSSCoverage();
186      expect(coverage.length).toBe(1);
187      expect(coverage[0].url).toBe('nicename.css');
188    });
189    it('should report multiple stylesheets', async () => {
190      const { page, server } = getTestState();
191
192      await page.coverage.startCSSCoverage();
193      await page.goto(server.PREFIX + '/csscoverage/multiple.html');
194      const coverage = await page.coverage.stopCSSCoverage();
195      expect(coverage.length).toBe(2);
196      coverage.sort((a, b) => a.url.localeCompare(b.url));
197      expect(coverage[0].url).toContain('/csscoverage/stylesheet1.css');
198      expect(coverage[1].url).toContain('/csscoverage/stylesheet2.css');
199    });
200    it('should report stylesheets that have no coverage', async () => {
201      const { page, server } = getTestState();
202
203      await page.coverage.startCSSCoverage();
204      await page.goto(server.PREFIX + '/csscoverage/unused.html');
205      const coverage = await page.coverage.stopCSSCoverage();
206      expect(coverage.length).toBe(1);
207      expect(coverage[0].url).toBe('unused.css');
208      expect(coverage[0].ranges.length).toBe(0);
209    });
210    it('should work with media queries', async () => {
211      const { page, server } = getTestState();
212
213      await page.coverage.startCSSCoverage();
214      await page.goto(server.PREFIX + '/csscoverage/media.html');
215      const coverage = await page.coverage.stopCSSCoverage();
216      expect(coverage.length).toBe(1);
217      expect(coverage[0].url).toContain('/csscoverage/media.html');
218      expect(coverage[0].ranges).toEqual([{ start: 17, end: 38 }]);
219    });
220    it('should work with complicated usecases', async () => {
221      const { page, server } = getTestState();
222
223      await page.coverage.startCSSCoverage();
224      await page.goto(server.PREFIX + '/csscoverage/involved.html');
225      const coverage = await page.coverage.stopCSSCoverage();
226      expect(
227        JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':<PORT>/')
228      ).toBeGolden('csscoverage-involved.txt');
229    });
230    it('should ignore injected stylesheets', async () => {
231      const { page } = getTestState();
232
233      await page.coverage.startCSSCoverage();
234      await page.addStyleTag({ content: 'body { margin: 10px;}' });
235      // trigger style recalc
236      const margin = await page.evaluate(
237        () => window.getComputedStyle(document.body).margin
238      );
239      expect(margin).toBe('10px');
240      const coverage = await page.coverage.stopCSSCoverage();
241      expect(coverage.length).toBe(0);
242    });
243    it('should work with a recently loaded stylesheet', async () => {
244      const { page, server } = getTestState();
245
246      await page.coverage.startCSSCoverage();
247      await page.evaluate<(url: string) => Promise<void>>(async (url) => {
248        document.body.textContent = 'hello, world';
249
250        const link = document.createElement('link');
251        link.rel = 'stylesheet';
252        link.href = url;
253        document.head.appendChild(link);
254        await new Promise((x) => (link.onload = x));
255      }, server.PREFIX + '/csscoverage/stylesheet1.css');
256      const coverage = await page.coverage.stopCSSCoverage();
257      expect(coverage.length).toBe(1);
258    });
259    describe('resetOnNavigation', function () {
260      it('should report stylesheets across navigations', async () => {
261        const { page, server } = getTestState();
262
263        await page.coverage.startCSSCoverage({ resetOnNavigation: false });
264        await page.goto(server.PREFIX + '/csscoverage/multiple.html');
265        await page.goto(server.EMPTY_PAGE);
266        const coverage = await page.coverage.stopCSSCoverage();
267        expect(coverage.length).toBe(2);
268      });
269      it('should NOT report scripts across navigations', async () => {
270        const { page, server } = getTestState();
271
272        await page.coverage.startCSSCoverage(); // Enabled by default.
273        await page.goto(server.PREFIX + '/csscoverage/multiple.html');
274        await page.goto(server.EMPTY_PAGE);
275        const coverage = await page.coverage.stopCSSCoverage();
276        expect(coverage.length).toBe(0);
277      });
278    });
279  });
280});
281