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  itFailsFirefox,
23} from './mocha-utils'; // eslint-disable-line import/extensions
24import utils from './utils.js';
25
26describe('Page.click', function () {
27  setupTestBrowserHooks();
28  setupTestPageAndContextHooks();
29  it('should click the button', async () => {
30    const { page, server } = getTestState();
31
32    await page.goto(server.PREFIX + '/input/button.html');
33    await page.click('button');
34    expect(await page.evaluate(() => globalThis.result)).toBe('Clicked');
35  });
36  it('should click svg', async () => {
37    const { page } = getTestState();
38
39    await page.setContent(`
40        <svg height="100" width="100">
41          <circle onclick="javascript:window.__CLICKED=42" cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
42        </svg>
43      `);
44    await page.click('circle');
45    expect(await page.evaluate(() => globalThis.__CLICKED)).toBe(42);
46  });
47  it(
48    'should click the button if window.Node is removed',
49    async () => {
50      const { page, server } = getTestState();
51
52      await page.goto(server.PREFIX + '/input/button.html');
53      await page.evaluate(() => delete window.Node);
54      await page.click('button');
55      expect(await page.evaluate(() => globalThis.result)).toBe('Clicked');
56    }
57  );
58  // @see https://github.com/puppeteer/puppeteer/issues/4281
59  it('should click on a span with an inline element inside', async () => {
60    const { page } = getTestState();
61
62    await page.setContent(`
63        <style>
64        span::before {
65          content: 'q';
66        }
67        </style>
68        <span onclick='javascript:window.CLICKED=42'></span>
69      `);
70    await page.click('span');
71    expect(await page.evaluate(() => globalThis.CLICKED)).toBe(42);
72  });
73  it('should not throw UnhandledPromiseRejection when page closes', async () => {
74    const { page } = getTestState();
75
76    const newPage = await page.browser().newPage();
77    await Promise.all([newPage.close(), newPage.mouse.click(1, 2)]).catch(
78      () => {}
79    );
80  });
81  it('should click the button after navigation ', async () => {
82    const { page, server } = getTestState();
83
84    await page.goto(server.PREFIX + '/input/button.html');
85    await page.click('button');
86    await page.goto(server.PREFIX + '/input/button.html');
87    await page.click('button');
88    expect(await page.evaluate(() => globalThis.result)).toBe('Clicked');
89  });
90  it('should click with disabled javascript', async () => {
91    const { page, server } = getTestState();
92
93    await page.setJavaScriptEnabled(false);
94    await page.goto(server.PREFIX + '/wrappedlink.html');
95    await Promise.all([page.click('a'), page.waitForNavigation()]);
96    expect(page.url()).toBe(server.PREFIX + '/wrappedlink.html#clicked');
97  });
98  it('should click when one of inline box children is outside of viewport', async () => {
99    const { page } = getTestState();
100
101    await page.setContent(`
102        <style>
103        i {
104          position: absolute;
105          top: -1000px;
106        }
107        </style>
108        <span onclick='javascript:window.CLICKED = 42;'><i>woof</i><b>doggo</b></span>
109      `);
110    await page.click('span');
111    expect(await page.evaluate(() => globalThis.CLICKED)).toBe(42);
112  });
113  it('should select the text by triple clicking', async () => {
114    const { page, server } = getTestState();
115
116    await page.goto(server.PREFIX + '/input/textarea.html');
117    await page.focus('textarea');
118    const text =
119      "This is the text that we are going to try to select. Let's see how it goes.";
120    await page.keyboard.type(text);
121    await page.click('textarea');
122    await page.click('textarea', { clickCount: 2 });
123    await page.click('textarea', { clickCount: 3 });
124    expect(
125      await page.evaluate(() => {
126        const textarea = document.querySelector('textarea');
127        return textarea.value.substring(
128          textarea.selectionStart,
129          textarea.selectionEnd
130        );
131      })
132    ).toBe(text);
133  });
134  it('should click offscreen buttons', async () => {
135    const { page, server } = getTestState();
136
137    await page.goto(server.PREFIX + '/offscreenbuttons.html');
138    const messages = [];
139    page.on('console', (msg) => messages.push(msg.text()));
140    for (let i = 0; i < 11; ++i) {
141      // We might've scrolled to click a button - reset to (0, 0).
142      await page.evaluate(() => window.scrollTo(0, 0));
143      await page.click(`#btn${i}`);
144    }
145    expect(messages).toEqual([
146      'button #0 clicked',
147      'button #1 clicked',
148      'button #2 clicked',
149      'button #3 clicked',
150      'button #4 clicked',
151      'button #5 clicked',
152      'button #6 clicked',
153      'button #7 clicked',
154      'button #8 clicked',
155      'button #9 clicked',
156      'button #10 clicked',
157    ]);
158  });
159
160  it('should click wrapped links', async () => {
161    const { page, server } = getTestState();
162
163    await page.goto(server.PREFIX + '/wrappedlink.html');
164    await page.click('a');
165    expect(await page.evaluate(() => globalThis.__clicked)).toBe(true);
166  });
167
168  it('should click on checkbox input and toggle', async () => {
169    const { page, server } = getTestState();
170
171    await page.goto(server.PREFIX + '/input/checkbox.html');
172    expect(await page.evaluate(() => globalThis.result.check)).toBe(null);
173    await page.click('input#agree');
174    expect(await page.evaluate(() => globalThis.result.check)).toBe(true);
175    expect(await page.evaluate(() => globalThis.result.events)).toEqual([
176      'mouseover',
177      'mouseenter',
178      'mousemove',
179      'mousedown',
180      'mouseup',
181      'click',
182      'input',
183      'change',
184    ]);
185    await page.click('input#agree');
186    expect(await page.evaluate(() => globalThis.result.check)).toBe(false);
187  });
188
189  it('should click on checkbox label and toggle', async () => {
190    const { page, server } = getTestState();
191
192    await page.goto(server.PREFIX + '/input/checkbox.html');
193    expect(await page.evaluate(() => globalThis.result.check)).toBe(null);
194    await page.click('label[for="agree"]');
195    expect(await page.evaluate(() => globalThis.result.check)).toBe(true);
196    expect(await page.evaluate(() => globalThis.result.events)).toEqual([
197      'click',
198      'input',
199      'change',
200    ]);
201    await page.click('label[for="agree"]');
202    expect(await page.evaluate(() => globalThis.result.check)).toBe(false);
203  });
204
205  it('should fail to click a missing button', async () => {
206    const { page, server } = getTestState();
207
208    await page.goto(server.PREFIX + '/input/button.html');
209    let error = null;
210    await page
211      .click('button.does-not-exist')
212      .catch((error_) => (error = error_));
213    expect(error.message).toBe(
214      'No node found for selector: button.does-not-exist'
215    );
216  });
217  // @see https://github.com/puppeteer/puppeteer/issues/161
218  it('should not hang with touch-enabled viewports', async () => {
219    const { page, puppeteer } = getTestState();
220
221    await page.setViewport(puppeteer.devices['iPhone 6'].viewport);
222    await page.mouse.down();
223    await page.mouse.move(100, 10);
224    await page.mouse.up();
225  });
226  it('should scroll and click the button', async () => {
227    const { page, server } = getTestState();
228
229    await page.goto(server.PREFIX + '/input/scrollable.html');
230    await page.click('#button-5');
231    expect(
232      await page.evaluate(() => document.querySelector('#button-5').textContent)
233    ).toBe('clicked');
234    await page.click('#button-80');
235    expect(
236      await page.evaluate(
237        () => document.querySelector('#button-80').textContent
238      )
239    ).toBe('clicked');
240  });
241  // See https://github.com/puppeteer/puppeteer/issues/7175
242  it('should double click the button', async () => {
243    const { page, server } = getTestState();
244
245    await page.goto(server.PREFIX + '/input/button.html');
246    await page.evaluate(() => {
247      globalThis.double = false;
248      const button = document.querySelector('button');
249      button.addEventListener('dblclick', () => {
250        globalThis.double = true;
251      });
252    });
253    const button = await page.$('button');
254    await button.click({ clickCount: 2 });
255    expect(await page.evaluate('double')).toBe(true);
256    expect(await page.evaluate('result')).toBe('Clicked');
257  });
258  it('should click a partially obscured button', async () => {
259    const { page, server } = getTestState();
260
261    await page.goto(server.PREFIX + '/input/button.html');
262    await page.evaluate(() => {
263      const button = document.querySelector('button');
264      button.textContent = 'Some really long text that will go offscreen';
265      button.style.position = 'absolute';
266      button.style.left = '368px';
267    });
268    await page.click('button');
269    expect(await page.evaluate(() => globalThis.result)).toBe('Clicked');
270  });
271  it('should click a rotated button', async () => {
272    const { page, server } = getTestState();
273
274    await page.goto(server.PREFIX + '/input/rotatedButton.html');
275    await page.click('button');
276    expect(await page.evaluate(() => globalThis.result)).toBe('Clicked');
277  });
278  it('should fire contextmenu event on right click', async () => {
279    const { page, server } = getTestState();
280
281    await page.goto(server.PREFIX + '/input/scrollable.html');
282    await page.click('#button-8', { button: 'right' });
283    expect(
284      await page.evaluate(() => document.querySelector('#button-8').textContent)
285    ).toBe('context menu');
286  });
287  // @see https://github.com/puppeteer/puppeteer/issues/206
288  it('should click links which cause navigation', async () => {
289    const { page, server } = getTestState();
290
291    await page.setContent(`<a href="${server.EMPTY_PAGE}">empty.html</a>`);
292    // This await should not hang.
293    await page.click('a');
294  });
295  it('should click the button inside an iframe', async () => {
296    const { page, server } = getTestState();
297
298    await page.goto(server.EMPTY_PAGE);
299    await page.setContent('<div style="width:100px;height:100px">spacer</div>');
300    await utils.attachFrame(
301      page,
302      'button-test',
303      server.PREFIX + '/input/button.html'
304    );
305    const frame = page.frames()[1];
306    const button = await frame.$('button');
307    await button.click();
308    expect(await frame.evaluate(() => globalThis.result)).toBe('Clicked');
309  });
310  // @see https://github.com/puppeteer/puppeteer/issues/4110
311  xit('should click the button with fixed position inside an iframe', async () => {
312    const { page, server } = getTestState();
313
314    await page.goto(server.EMPTY_PAGE);
315    await page.setViewport({ width: 500, height: 500 });
316    await page.setContent(
317      '<div style="width:100px;height:2000px">spacer</div>'
318    );
319    await utils.attachFrame(
320      page,
321      'button-test',
322      server.CROSS_PROCESS_PREFIX + '/input/button.html'
323    );
324    const frame = page.frames()[1];
325    await frame.$eval('button', (button: HTMLElement) =>
326      button.style.setProperty('position', 'fixed')
327    );
328    await frame.click('button');
329    expect(await frame.evaluate(() => globalThis.result)).toBe('Clicked');
330  });
331  it(
332    'should click the button with deviceScaleFactor set',
333    async () => {
334      const { page, server } = getTestState();
335
336      await page.setViewport({ width: 400, height: 400, deviceScaleFactor: 5 });
337      expect(await page.evaluate(() => window.devicePixelRatio)).toBe(5);
338      await page.setContent(
339        '<div style="width:100px;height:100px">spacer</div>'
340      );
341      await utils.attachFrame(
342        page,
343        'button-test',
344        server.PREFIX + '/input/button.html'
345      );
346      const frame = page.frames()[1];
347      const button = await frame.$('button');
348      await button.click();
349      expect(await frame.evaluate(() => globalThis.result)).toBe('Clicked');
350    }
351  );
352});
353