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 utils from './utils.js';
18const { waitEvent } = utils;
19import expect from 'expect';
20import {
21  getTestState,
22  setupTestBrowserHooks,
23  setupTestPageAndContextHooks,
24  itFailsFirefox,
25} from './mocha-utils'; // eslint-disable-line import/extensions
26import { Target } from '../lib/cjs/puppeteer/common/Target.js';
27
28describe('Target', function () {
29  setupTestBrowserHooks();
30  setupTestPageAndContextHooks();
31
32  it('Browser.targets should return all of the targets', async () => {
33    const { browser } = getTestState();
34
35    // The pages will be the testing page and the original newtab page
36    const targets = browser.targets();
37    expect(
38      targets.some(
39        (target) => target.type() === 'page' && target.url() === 'about:blank'
40      )
41    ).toBeTruthy();
42    expect(targets.some((target) => target.type() === 'browser')).toBeTruthy();
43  });
44  it('Browser.pages should return all of the pages', async () => {
45    const { page, context } = getTestState();
46
47    // The pages will be the testing page
48    const allPages = await context.pages();
49    expect(allPages.length).toBe(1);
50    expect(allPages).toContain(page);
51  });
52  it('should contain browser target', async () => {
53    const { browser } = getTestState();
54
55    const targets = browser.targets();
56    const browserTarget = targets.find((target) => target.type() === 'browser');
57    expect(browserTarget).toBeTruthy();
58  });
59  it('should be able to use the default page in the browser', async () => {
60    const { page, browser } = getTestState();
61
62    // The pages will be the testing page and the original newtab page
63    const allPages = await browser.pages();
64    const originalPage = allPages.find((p) => p !== page);
65    expect(
66      await originalPage.evaluate(() => ['Hello', 'world'].join(' '))
67    ).toBe('Hello world');
68    expect(await originalPage.$('body')).toBeTruthy();
69  });
70  it(
71    'should report when a new page is created and closed',
72    async () => {
73      const { page, server, context } = getTestState();
74
75      const [otherPage] = await Promise.all([
76        context
77          .waitForTarget(
78            (target) =>
79              target.url() === server.CROSS_PROCESS_PREFIX + '/empty.html'
80          )
81          .then((target) => target.page()),
82        page.evaluate(
83          (url: string) => window.open(url),
84          server.CROSS_PROCESS_PREFIX + '/empty.html'
85        ),
86      ]);
87      expect(otherPage.url()).toContain(server.CROSS_PROCESS_PREFIX);
88      expect(await otherPage.evaluate(() => ['Hello', 'world'].join(' '))).toBe(
89        'Hello world'
90      );
91      expect(await otherPage.$('body')).toBeTruthy();
92
93      let allPages = await context.pages();
94      expect(allPages).toContain(page);
95      expect(allPages).toContain(otherPage);
96
97      const closePagePromise = new Promise((fulfill) =>
98        context.once('targetdestroyed', (target) => fulfill(target.page()))
99      );
100      await otherPage.close();
101      expect(await closePagePromise).toBe(otherPage);
102
103      allPages = await Promise.all(
104        context.targets().map((target) => target.page())
105      );
106      expect(allPages).toContain(page);
107      expect(allPages).not.toContain(otherPage);
108    }
109  );
110  it(
111    'should report when a service worker is created and destroyed',
112    async () => {
113      const { page, server, context } = getTestState();
114
115      await page.goto(server.EMPTY_PAGE);
116      const createdTarget = new Promise<Target>((fulfill) =>
117        context.once('targetcreated', (target) => fulfill(target))
118      );
119
120      await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html');
121
122      expect((await createdTarget).type()).toBe('service_worker');
123      expect((await createdTarget).url()).toBe(
124        server.PREFIX + '/serviceworkers/empty/sw.js'
125      );
126
127      const destroyedTarget = new Promise((fulfill) =>
128        context.once('targetdestroyed', (target) => fulfill(target))
129      );
130      await page.evaluate(() =>
131        globalThis.registrationPromise.then((registration) =>
132          registration.unregister()
133        )
134      );
135      expect(await destroyedTarget).toBe(await createdTarget);
136    }
137  );
138  it('should create a worker from a service worker', async () => {
139    const { page, server, context } = getTestState();
140
141    await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html');
142
143    const target = await context.waitForTarget(
144      (target) => target.type() === 'service_worker'
145    );
146    const worker = await target.worker();
147    expect(await worker.evaluate(() => self.toString())).toBe(
148      '[object ServiceWorkerGlobalScope]'
149    );
150  });
151  it('should create a worker from a shared worker', async () => {
152    const { page, server, context } = getTestState();
153
154    await page.goto(server.EMPTY_PAGE);
155    await page.evaluate(() => {
156      new SharedWorker('data:text/javascript,console.log("hi")');
157    });
158    const target = await context.waitForTarget(
159      (target) => target.type() === 'shared_worker'
160    );
161    const worker = await target.worker();
162    expect(await worker.evaluate(() => self.toString())).toBe(
163      '[object SharedWorkerGlobalScope]'
164    );
165  });
166  it('should report when a target url changes', async () => {
167    const { page, server, context } = getTestState();
168
169    await page.goto(server.EMPTY_PAGE);
170    let changedTarget = new Promise<Target>((fulfill) =>
171      context.once('targetchanged', (target) => fulfill(target))
172    );
173    await page.goto(server.CROSS_PROCESS_PREFIX + '/');
174    expect((await changedTarget).url()).toBe(server.CROSS_PROCESS_PREFIX + '/');
175
176    changedTarget = new Promise((fulfill) =>
177      context.once('targetchanged', (target) => fulfill(target))
178    );
179    await page.goto(server.EMPTY_PAGE);
180    expect((await changedTarget).url()).toBe(server.EMPTY_PAGE);
181  });
182  it('should not report uninitialized pages', async () => {
183    const { context } = getTestState();
184
185    let targetChanged = false;
186    const listener = () => (targetChanged = true);
187    context.on('targetchanged', listener);
188    const targetPromise = new Promise<Target>((fulfill) =>
189      context.once('targetcreated', (target) => fulfill(target))
190    );
191    const newPagePromise = context.newPage();
192    const target = await targetPromise;
193    expect(target.url()).toBe('about:blank');
194
195    const newPage = await newPagePromise;
196    const targetPromise2 = new Promise<Target>((fulfill) =>
197      context.once('targetcreated', (target) => fulfill(target))
198    );
199    const evaluatePromise = newPage.evaluate(() => window.open('about:blank'));
200    const target2 = await targetPromise2;
201    expect(target2.url()).toBe('about:blank');
202    await evaluatePromise;
203    await newPage.close();
204    expect(targetChanged).toBe(false);
205    context.removeListener('targetchanged', listener);
206  });
207  it(
208    'should not crash while redirecting if original request was missed',
209    async () => {
210      const { page, server, context } = getTestState();
211
212      let serverResponse = null;
213      server.setRoute('/one-style.css', (req, res) => (serverResponse = res));
214      // Open a new page. Use window.open to connect to the page later.
215      await Promise.all([
216        page.evaluate(
217          (url: string) => window.open(url),
218          server.PREFIX + '/one-style.html'
219        ),
220        server.waitForRequest('/one-style.css'),
221      ]);
222      // Connect to the opened page.
223      const target = await context.waitForTarget((target) =>
224        target.url().includes('one-style.html')
225      );
226      const newPage = await target.page();
227      // Issue a redirect.
228      serverResponse.writeHead(302, { location: '/injectedstyle.css' });
229      serverResponse.end();
230      // Wait for the new page to load.
231      await waitEvent(newPage, 'load');
232      // Cleanup.
233      await newPage.close();
234    }
235  );
236  it('should have an opener', async () => {
237    const { page, server, context } = getTestState();
238
239    await page.goto(server.EMPTY_PAGE);
240    const [createdTarget] = await Promise.all([
241      new Promise<Target>((fulfill) =>
242        context.once('targetcreated', (target) => fulfill(target))
243      ),
244      page.goto(server.PREFIX + '/popup/window-open.html'),
245    ]);
246    expect((await createdTarget.page()).url()).toBe(
247      server.PREFIX + '/popup/popup.html'
248    );
249    expect(createdTarget.opener()).toBe(page.target());
250    expect(page.target().opener()).toBe(null);
251  });
252
253  describe('Browser.waitForTarget', () => {
254    it('should wait for a target', async () => {
255      const { browser, puppeteer, server } = getTestState();
256
257      let resolved = false;
258      const targetPromise = browser.waitForTarget(
259        (target) => target.url() === server.EMPTY_PAGE
260      );
261      targetPromise
262        .then(() => (resolved = true))
263        .catch((error) => {
264          resolved = true;
265          if (error instanceof puppeteer.errors.TimeoutError) {
266            console.error(error);
267          } else throw error;
268        });
269      const page = await browser.newPage();
270      expect(resolved).toBe(false);
271      await page.goto(server.EMPTY_PAGE);
272      try {
273        const target = await targetPromise;
274        expect(await target.page()).toBe(page);
275      } catch (error) {
276        if (error instanceof puppeteer.errors.TimeoutError) {
277          console.error(error);
278        } else throw error;
279      }
280      await page.close();
281    });
282    it('should timeout waiting for a non-existent target', async () => {
283      const { browser, server, puppeteer } = getTestState();
284
285      let error = null;
286      await browser
287        .waitForTarget((target) => target.url() === server.EMPTY_PAGE, {
288          timeout: 1,
289        })
290        .catch((error_) => (error = error_));
291      expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
292    });
293  });
294});
295