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    describe('includeRawScriptCoverage', function () {
161      it('should not include rawScriptCoverage field when disabled', async () => {
162        const { page, server } = getTestState();
163        await page.coverage.startJSCoverage();
164        await page.goto(server.PREFIX + '/jscoverage/simple.html', {
165          waitUntil: 'networkidle0',
166        });
167        const coverage = await page.coverage.stopJSCoverage();
168        expect(coverage.length).toBe(1);
169        expect(coverage[0].rawScriptCoverage).toBeUndefined();
170      });
171      it('should include rawScriptCoverage field when enabled', async () => {
172        const { page, server } = getTestState();
173        await page.coverage.startJSCoverage({
174          includeRawScriptCoverage: true,
175        });
176        await page.goto(server.PREFIX + '/jscoverage/simple.html', {
177          waitUntil: 'networkidle0',
178        });
179        const coverage = await page.coverage.stopJSCoverage();
180        expect(coverage.length).toBe(1);
181        expect(coverage[0].rawScriptCoverage).toBeTruthy();
182      });
183    });
184    // @see https://crbug.com/990945
185    xit('should not hang when there is a debugger statement', async () => {
186      const { page, server } = getTestState();
187
188      await page.coverage.startJSCoverage();
189      await page.goto(server.EMPTY_PAGE);
190      await page.evaluate(() => {
191        debugger; // eslint-disable-line no-debugger
192      });
193      await page.coverage.stopJSCoverage();
194    });
195  });
196
197  describeChromeOnly('CSSCoverage', function () {
198    setupTestBrowserHooks();
199    setupTestPageAndContextHooks();
200
201    it('should work', async () => {
202      const { page, server } = getTestState();
203
204      await page.coverage.startCSSCoverage();
205      await page.goto(server.PREFIX + '/csscoverage/simple.html');
206      const coverage = await page.coverage.stopCSSCoverage();
207      expect(coverage.length).toBe(1);
208      expect(coverage[0].url).toContain('/csscoverage/simple.html');
209      expect(coverage[0].ranges).toEqual([{ start: 1, end: 22 }]);
210      const range = coverage[0].ranges[0];
211      expect(coverage[0].text.substring(range.start, range.end)).toBe(
212        'div { color: green; }'
213      );
214    });
215    it('should report sourceURLs', async () => {
216      const { page, server } = getTestState();
217
218      await page.coverage.startCSSCoverage();
219      await page.goto(server.PREFIX + '/csscoverage/sourceurl.html');
220      const coverage = await page.coverage.stopCSSCoverage();
221      expect(coverage.length).toBe(1);
222      expect(coverage[0].url).toBe('nicename.css');
223    });
224    it('should report multiple stylesheets', async () => {
225      const { page, server } = getTestState();
226
227      await page.coverage.startCSSCoverage();
228      await page.goto(server.PREFIX + '/csscoverage/multiple.html');
229      const coverage = await page.coverage.stopCSSCoverage();
230      expect(coverage.length).toBe(2);
231      coverage.sort((a, b) => a.url.localeCompare(b.url));
232      expect(coverage[0].url).toContain('/csscoverage/stylesheet1.css');
233      expect(coverage[1].url).toContain('/csscoverage/stylesheet2.css');
234    });
235    it('should report stylesheets that have no coverage', async () => {
236      const { page, server } = getTestState();
237
238      await page.coverage.startCSSCoverage();
239      await page.goto(server.PREFIX + '/csscoverage/unused.html');
240      const coverage = await page.coverage.stopCSSCoverage();
241      expect(coverage.length).toBe(1);
242      expect(coverage[0].url).toBe('unused.css');
243      expect(coverage[0].ranges.length).toBe(0);
244    });
245    it('should work with media queries', async () => {
246      const { page, server } = getTestState();
247
248      await page.coverage.startCSSCoverage();
249      await page.goto(server.PREFIX + '/csscoverage/media.html');
250      const coverage = await page.coverage.stopCSSCoverage();
251      expect(coverage.length).toBe(1);
252      expect(coverage[0].url).toContain('/csscoverage/media.html');
253      expect(coverage[0].ranges).toEqual([{ start: 17, end: 38 }]);
254    });
255    it('should work with complicated usecases', async () => {
256      const { page, server } = getTestState();
257
258      await page.coverage.startCSSCoverage();
259      await page.goto(server.PREFIX + '/csscoverage/involved.html');
260      const coverage = await page.coverage.stopCSSCoverage();
261      expect(
262        JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':<PORT>/')
263      ).toBeGolden('csscoverage-involved.txt');
264    });
265    it('should ignore injected stylesheets', async () => {
266      const { page } = getTestState();
267
268      await page.coverage.startCSSCoverage();
269      await page.addStyleTag({ content: 'body { margin: 10px;}' });
270      // trigger style recalc
271      const margin = await page.evaluate(
272        () => window.getComputedStyle(document.body).margin
273      );
274      expect(margin).toBe('10px');
275      const coverage = await page.coverage.stopCSSCoverage();
276      expect(coverage.length).toBe(0);
277    });
278    it('should work with a recently loaded stylesheet', async () => {
279      const { page, server } = getTestState();
280
281      await page.coverage.startCSSCoverage();
282      await page.evaluate<(url: string) => Promise<void>>(async (url) => {
283        document.body.textContent = 'hello, world';
284
285        const link = document.createElement('link');
286        link.rel = 'stylesheet';
287        link.href = url;
288        document.head.appendChild(link);
289        await new Promise((x) => (link.onload = x));
290      }, server.PREFIX + '/csscoverage/stylesheet1.css');
291      const coverage = await page.coverage.stopCSSCoverage();
292      expect(coverage.length).toBe(1);
293    });
294    describe('resetOnNavigation', function () {
295      it('should report stylesheets across navigations', async () => {
296        const { page, server } = getTestState();
297
298        await page.coverage.startCSSCoverage({ resetOnNavigation: false });
299        await page.goto(server.PREFIX + '/csscoverage/multiple.html');
300        await page.goto(server.EMPTY_PAGE);
301        const coverage = await page.coverage.stopCSSCoverage();
302        expect(coverage.length).toBe(2);
303      });
304      it('should NOT report scripts across navigations', async () => {
305        const { page, server } = getTestState();
306
307        await page.coverage.startCSSCoverage(); // Enabled by default.
308        await page.goto(server.PREFIX + '/csscoverage/multiple.html');
309        await page.goto(server.EMPTY_PAGE);
310        const coverage = await page.coverage.stopCSSCoverage();
311        expect(coverage.length).toBe(0);
312      });
313    });
314  });
315});
316