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';
18import sinon from 'sinon';
19import expect from 'expect';
20import {
21  getTestState,
22  setupTestBrowserHooks,
23  setupTestPageAndContextHooks,
24  itFailsFirefox,
25} from './mocha-utils'; // eslint-disable-line import/extensions
26
27describe('waittask specs', function () {
28  setupTestBrowserHooks();
29  setupTestPageAndContextHooks();
30
31  describe('Page.waitFor', function () {
32    /* This method is deprecated but we don't want the warnings showing up in
33     * tests. Until we remove this method we still want to ensure we don't break
34     * it.
35     */
36    beforeEach(() => sinon.stub(console, 'warn').callsFake(() => {}));
37
38    it('should wait for selector', async () => {
39      const { page, server } = getTestState();
40
41      let found = false;
42      const waitFor = page.waitFor('div').then(() => (found = true));
43      await page.goto(server.EMPTY_PAGE);
44      expect(found).toBe(false);
45      await page.goto(server.PREFIX + '/grid.html');
46      await waitFor;
47      expect(found).toBe(true);
48    });
49
50    it('should wait for an xpath', async () => {
51      const { page, server } = getTestState();
52
53      let found = false;
54      const waitFor = page.waitFor('//div').then(() => (found = true));
55      await page.goto(server.EMPTY_PAGE);
56      expect(found).toBe(false);
57      await page.goto(server.PREFIX + '/grid.html');
58      await waitFor;
59      expect(found).toBe(true);
60    });
61    it('should not allow you to select an element with single slash xpath', async () => {
62      const { page } = getTestState();
63
64      await page.setContent(`<div>some text</div>`);
65      let error = null;
66      await page.waitFor('/html/body/div').catch((error_) => (error = error_));
67      expect(error).toBeTruthy();
68    });
69    it('should timeout', async () => {
70      const { page } = getTestState();
71
72      const startTime = Date.now();
73      const timeout = 42;
74      await page.waitFor(timeout);
75      expect(Date.now() - startTime).not.toBeLessThan(timeout / 2);
76    });
77    it('should work with multiline body', async () => {
78      const { page } = getTestState();
79
80      const result = await page.waitForFunction(`
81        (() => true)()
82      `);
83      expect(await result.jsonValue()).toBe(true);
84    });
85    it('should wait for predicate', async () => {
86      const { page } = getTestState();
87
88      await Promise.all([
89        page.waitFor(() => window.innerWidth < 100),
90        page.setViewport({ width: 10, height: 10 }),
91      ]);
92    });
93    it('should throw when unknown type', async () => {
94      const { page } = getTestState();
95
96      let error = null;
97      // @ts-expect-error purposefully passing bad type for test
98      await page.waitFor({ foo: 'bar' }).catch((error_) => (error = error_));
99      expect(error.message).toContain('Unsupported target type');
100    });
101    it('should wait for predicate with arguments', async () => {
102      const { page } = getTestState();
103
104      await page.waitFor((arg1, arg2) => arg1 !== arg2, {}, 1, 2);
105    });
106
107    it('should log a deprecation warning', async () => {
108      const { page } = getTestState();
109
110      await page.waitFor(() => true);
111
112      const consoleWarnStub = console.warn as sinon.SinonSpy;
113
114      expect(consoleWarnStub.calledOnce).toBe(true);
115      expect(
116        consoleWarnStub.firstCall.calledWith(
117          'waitFor is deprecated and will be removed in a future release. See https://github.com/puppeteer/puppeteer/issues/6214 for details and how to migrate your code.'
118        )
119      ).toBe(true);
120      expect((console.warn as sinon.SinonSpy).calledOnce).toBe(true);
121    });
122  });
123
124  describe('Frame.waitForFunction', function () {
125    it('should accept a string', async () => {
126      const { page } = getTestState();
127
128      const watchdog = page.waitForFunction('window.__FOO === 1');
129      await page.evaluate(() => (globalThis.__FOO = 1));
130      await watchdog;
131    });
132    it('should work when resolved right before execution context disposal', async () => {
133      const { page } = getTestState();
134
135      await page.evaluateOnNewDocument(() => (globalThis.__RELOADED = true));
136      await page.waitForFunction(() => {
137        if (!globalThis.__RELOADED) window.location.reload();
138        return true;
139      });
140    });
141    it('should poll on interval', async () => {
142      const { page } = getTestState();
143
144      let success = false;
145      const startTime = Date.now();
146      const polling = 100;
147      const watchdog = page
148        .waitForFunction(() => globalThis.__FOO === 'hit', { polling })
149        .then(() => (success = true));
150      await page.evaluate(() => (globalThis.__FOO = 'hit'));
151      expect(success).toBe(false);
152      await page.evaluate(() =>
153        document.body.appendChild(document.createElement('div'))
154      );
155      await watchdog;
156      expect(Date.now() - startTime).not.toBeLessThan(polling / 2);
157    });
158    it('should poll on interval async', async () => {
159      const { page } = getTestState();
160      let success = false;
161      const startTime = Date.now();
162      const polling = 100;
163      const watchdog = page
164        .waitForFunction(async () => globalThis.__FOO === 'hit', { polling })
165        .then(() => (success = true));
166      await page.evaluate(async () => (globalThis.__FOO = 'hit'));
167      expect(success).toBe(false);
168      await page.evaluate(async () =>
169        document.body.appendChild(document.createElement('div'))
170      );
171      await watchdog;
172      expect(Date.now() - startTime).not.toBeLessThan(polling / 2);
173    });
174    it('should poll on mutation', async () => {
175      const { page } = getTestState();
176
177      let success = false;
178      const watchdog = page
179        .waitForFunction(() => globalThis.__FOO === 'hit', {
180          polling: 'mutation',
181        })
182        .then(() => (success = true));
183      await page.evaluate(() => (globalThis.__FOO = 'hit'));
184      expect(success).toBe(false);
185      await page.evaluate(() =>
186        document.body.appendChild(document.createElement('div'))
187      );
188      await watchdog;
189    });
190    it('should poll on mutation async', async () => {
191      const { page } = getTestState();
192
193      let success = false;
194      const watchdog = page
195        .waitForFunction(async () => globalThis.__FOO === 'hit', {
196          polling: 'mutation',
197        })
198        .then(() => (success = true));
199      await page.evaluate(async () => (globalThis.__FOO = 'hit'));
200      expect(success).toBe(false);
201      await page.evaluate(async () =>
202        document.body.appendChild(document.createElement('div'))
203      );
204      await watchdog;
205    });
206    it('should poll on raf', async () => {
207      const { page } = getTestState();
208
209      const watchdog = page.waitForFunction(() => globalThis.__FOO === 'hit', {
210        polling: 'raf',
211      });
212      await page.evaluate(() => (globalThis.__FOO = 'hit'));
213      await watchdog;
214    });
215    it('should poll on raf async', async () => {
216      const { page } = getTestState();
217
218      const watchdog = page.waitForFunction(
219        async () => globalThis.__FOO === 'hit',
220        {
221          polling: 'raf',
222        }
223      );
224      await page.evaluate(async () => (globalThis.__FOO = 'hit'));
225      await watchdog;
226    });
227    it('should work with strict CSP policy', async () => {
228      const { page, server } = getTestState();
229
230      server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
231      await page.goto(server.EMPTY_PAGE);
232      let error = null;
233      await Promise.all([
234        page
235          .waitForFunction(() => globalThis.__FOO === 'hit', { polling: 'raf' })
236          .catch((error_) => (error = error_)),
237        page.evaluate(() => (globalThis.__FOO = 'hit')),
238      ]);
239      expect(error).toBe(null);
240    });
241    it('should throw on bad polling value', async () => {
242      const { page } = getTestState();
243
244      let error = null;
245      try {
246        await page.waitForFunction(() => !!document.body, {
247          polling: 'unknown',
248        });
249      } catch (error_) {
250        error = error_;
251      }
252      expect(error).toBeTruthy();
253      expect(error.message).toContain('polling');
254    });
255    it('should throw negative polling interval', async () => {
256      const { page } = getTestState();
257
258      let error = null;
259      try {
260        await page.waitForFunction(() => !!document.body, { polling: -10 });
261      } catch (error_) {
262        error = error_;
263      }
264      expect(error).toBeTruthy();
265      expect(error.message).toContain('Cannot poll with non-positive interval');
266    });
267    it('should return the success value as a JSHandle', async () => {
268      const { page } = getTestState();
269
270      expect(await (await page.waitForFunction(() => 5)).jsonValue()).toBe(5);
271    });
272    it('should return the window as a success value', async () => {
273      const { page } = getTestState();
274
275      expect(await page.waitForFunction(() => window)).toBeTruthy();
276    });
277    it('should accept ElementHandle arguments', async () => {
278      const { page } = getTestState();
279
280      await page.setContent('<div></div>');
281      const div = await page.$('div');
282      let resolved = false;
283      const waitForFunction = page
284        .waitForFunction((element) => !element.parentElement, {}, div)
285        .then(() => (resolved = true));
286      expect(resolved).toBe(false);
287      await page.evaluate((element: HTMLElement) => element.remove(), div);
288      await waitForFunction;
289    });
290    it('should respect timeout', async () => {
291      const { page, puppeteer } = getTestState();
292
293      let error = null;
294      await page
295        .waitForFunction('false', { timeout: 10 })
296        .catch((error_) => (error = error_));
297      expect(error).toBeTruthy();
298      expect(error.message).toContain('waiting for function failed: timeout');
299      expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
300    });
301    it('should respect default timeout', async () => {
302      const { page, puppeteer } = getTestState();
303
304      page.setDefaultTimeout(1);
305      let error = null;
306      await page.waitForFunction('false').catch((error_) => (error = error_));
307      expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
308      expect(error.message).toContain('waiting for function failed: timeout');
309    });
310    it('should disable timeout when its set to 0', async () => {
311      const { page } = getTestState();
312
313      const watchdog = page.waitForFunction(
314        () => {
315          globalThis.__counter = (globalThis.__counter || 0) + 1;
316          return globalThis.__injected;
317        },
318        { timeout: 0, polling: 10 }
319      );
320      await page.waitForFunction(() => globalThis.__counter > 10);
321      await page.evaluate(() => (globalThis.__injected = true));
322      await watchdog;
323    });
324    it('should survive cross-process navigation', async () => {
325      const { page, server } = getTestState();
326
327      let fooFound = false;
328      const waitForFunction = page
329        .waitForFunction('globalThis.__FOO === 1')
330        .then(() => (fooFound = true));
331      await page.goto(server.EMPTY_PAGE);
332      expect(fooFound).toBe(false);
333      await page.reload();
334      expect(fooFound).toBe(false);
335      await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
336      expect(fooFound).toBe(false);
337      await page.evaluate(() => (globalThis.__FOO = 1));
338      await waitForFunction;
339      expect(fooFound).toBe(true);
340    });
341    it('should survive navigations', async () => {
342      const { page, server } = getTestState();
343
344      const watchdog = page.waitForFunction(() => globalThis.__done);
345      await page.goto(server.EMPTY_PAGE);
346      await page.goto(server.PREFIX + '/consolelog.html');
347      await page.evaluate(() => (globalThis.__done = true));
348      await watchdog;
349    });
350  });
351
352  describe('Page.waitForTimeout', () => {
353    it('waits for the given timeout before resolving', async () => {
354      const { page, server } = getTestState();
355      await page.goto(server.EMPTY_PAGE);
356      const startTime = Date.now();
357      await page.waitForTimeout(1000);
358      const endTime = Date.now();
359      /* In a perfect world endTime - startTime would be exactly 1000 but we
360       * expect some fluctuations and for it to be off by a little bit. So to
361       * avoid a flaky test we'll make sure it waited for roughly 1 second.
362       */
363      expect(endTime - startTime).toBeGreaterThan(700);
364      expect(endTime - startTime).toBeLessThan(1300);
365    });
366  });
367
368  describe('Frame.waitForTimeout', () => {
369    it('waits for the given timeout before resolving', async () => {
370      const { page, server } = getTestState();
371      await page.goto(server.EMPTY_PAGE);
372      const frame = page.mainFrame();
373      const startTime = Date.now();
374      await frame.waitForTimeout(1000);
375      const endTime = Date.now();
376      /* In a perfect world endTime - startTime would be exactly 1000 but we
377       * expect some fluctuations and for it to be off by a little bit. So to
378       * avoid a flaky test we'll make sure it waited for roughly 1 second
379       */
380      expect(endTime - startTime).toBeGreaterThan(700);
381      expect(endTime - startTime).toBeLessThan(1300);
382    });
383  });
384
385  describe('Frame.waitForSelector', function () {
386    const addElement = (tag) =>
387      document.body.appendChild(document.createElement(tag));
388
389    it('should immediately resolve promise if node exists', async () => {
390      const { page, server } = getTestState();
391
392      await page.goto(server.EMPTY_PAGE);
393      const frame = page.mainFrame();
394      await frame.waitForSelector('*');
395      await frame.evaluate(addElement, 'div');
396      await frame.waitForSelector('div');
397    });
398
399    it('should work with removed MutationObserver', async () => {
400      const { page } = getTestState();
401
402      await page.evaluate(() => delete window.MutationObserver);
403      const [handle] = await Promise.all([
404        page.waitForSelector('.zombo'),
405        page.setContent(`<div class='zombo'>anything</div>`),
406      ]);
407      expect(
408        await page.evaluate((x: HTMLElement) => x.textContent, handle)
409      ).toBe('anything');
410    });
411
412    it('should resolve promise when node is added', async () => {
413      const { page, server } = getTestState();
414
415      await page.goto(server.EMPTY_PAGE);
416      const frame = page.mainFrame();
417      const watchdog = frame.waitForSelector('div');
418      await frame.evaluate(addElement, 'br');
419      await frame.evaluate(addElement, 'div');
420      const eHandle = await watchdog;
421      const tagName = await eHandle
422        .getProperty('tagName')
423        .then((e) => e.jsonValue());
424      expect(tagName).toBe('DIV');
425    });
426
427    it('should work when node is added through innerHTML', async () => {
428      const { page, server } = getTestState();
429
430      await page.goto(server.EMPTY_PAGE);
431      const watchdog = page.waitForSelector('h3 div');
432      await page.evaluate(addElement, 'span');
433      await page.evaluate(
434        () =>
435          (document.querySelector('span').innerHTML = '<h3><div></div></h3>')
436      );
437      await watchdog;
438    });
439
440    it(
441      'Page.waitForSelector is shortcut for main frame',
442      async () => {
443        const { page, server } = getTestState();
444
445        await page.goto(server.EMPTY_PAGE);
446        await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
447        const otherFrame = page.frames()[1];
448        const watchdog = page.waitForSelector('div');
449        await otherFrame.evaluate(addElement, 'div');
450        await page.evaluate(addElement, 'div');
451        const eHandle = await watchdog;
452        expect(eHandle.executionContext().frame()).toBe(page.mainFrame());
453      }
454    );
455
456    it('should run in specified frame', async () => {
457      const { page, server } = getTestState();
458
459      await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
460      await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
461      const frame1 = page.frames()[1];
462      const frame2 = page.frames()[2];
463      const waitForSelectorPromise = frame2.waitForSelector('div');
464      await frame1.evaluate(addElement, 'div');
465      await frame2.evaluate(addElement, 'div');
466      const eHandle = await waitForSelectorPromise;
467      expect(eHandle.executionContext().frame()).toBe(frame2);
468    });
469
470    it('should throw when frame is detached', async () => {
471      const { page, server } = getTestState();
472
473      await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
474      const frame = page.frames()[1];
475      let waitError = null;
476      const waitPromise = frame
477        .waitForSelector('.box')
478        .catch((error) => (waitError = error));
479      await utils.detachFrame(page, 'frame1');
480      await waitPromise;
481      expect(waitError).toBeTruthy();
482      expect(waitError.message).toContain(
483        'waitForFunction failed: frame got detached.'
484      );
485    });
486    it('should survive cross-process navigation', async () => {
487      const { page, server } = getTestState();
488
489      let boxFound = false;
490      const waitForSelector = page
491        .waitForSelector('.box')
492        .then(() => (boxFound = true));
493      await page.goto(server.EMPTY_PAGE);
494      expect(boxFound).toBe(false);
495      await page.reload();
496      expect(boxFound).toBe(false);
497      await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
498      await waitForSelector;
499      expect(boxFound).toBe(true);
500    });
501    it('should wait for visible', async () => {
502      const { page } = getTestState();
503
504      let divFound = false;
505      const waitForSelector = page
506        .waitForSelector('div', { visible: true })
507        .then(() => (divFound = true));
508      await page.setContent(
509        `<div style='display: none; visibility: hidden;'>1</div>`
510      );
511      expect(divFound).toBe(false);
512      await page.evaluate(() =>
513        document.querySelector('div').style.removeProperty('display')
514      );
515      expect(divFound).toBe(false);
516      await page.evaluate(() =>
517        document.querySelector('div').style.removeProperty('visibility')
518      );
519      expect(await waitForSelector).toBe(true);
520      expect(divFound).toBe(true);
521    });
522    it('should wait for visible recursively', async () => {
523      const { page } = getTestState();
524
525      let divVisible = false;
526      const waitForSelector = page
527        .waitForSelector('div#inner', { visible: true })
528        .then(() => (divVisible = true));
529      await page.setContent(
530        `<div style='display: none; visibility: hidden;'><div id="inner">hi</div></div>`
531      );
532      expect(divVisible).toBe(false);
533      await page.evaluate(() =>
534        document.querySelector('div').style.removeProperty('display')
535      );
536      expect(divVisible).toBe(false);
537      await page.evaluate(() =>
538        document.querySelector('div').style.removeProperty('visibility')
539      );
540      expect(await waitForSelector).toBe(true);
541      expect(divVisible).toBe(true);
542    });
543    it('hidden should wait for visibility: hidden', async () => {
544      const { page } = getTestState();
545
546      let divHidden = false;
547      await page.setContent(`<div style='display: block;'></div>`);
548      const waitForSelector = page
549        .waitForSelector('div', { hidden: true })
550        .then(() => (divHidden = true));
551      await page.waitForSelector('div'); // do a round trip
552      expect(divHidden).toBe(false);
553      await page.evaluate(() =>
554        document.querySelector('div').style.setProperty('visibility', 'hidden')
555      );
556      expect(await waitForSelector).toBe(true);
557      expect(divHidden).toBe(true);
558    });
559    it('hidden should wait for display: none', async () => {
560      const { page } = getTestState();
561
562      let divHidden = false;
563      await page.setContent(`<div style='display: block;'></div>`);
564      const waitForSelector = page
565        .waitForSelector('div', { hidden: true })
566        .then(() => (divHidden = true));
567      await page.waitForSelector('div'); // do a round trip
568      expect(divHidden).toBe(false);
569      await page.evaluate(() =>
570        document.querySelector('div').style.setProperty('display', 'none')
571      );
572      expect(await waitForSelector).toBe(true);
573      expect(divHidden).toBe(true);
574    });
575    it('hidden should wait for removal', async () => {
576      const { page } = getTestState();
577
578      await page.setContent(`<div></div>`);
579      let divRemoved = false;
580      const waitForSelector = page
581        .waitForSelector('div', { hidden: true })
582        .then(() => (divRemoved = true));
583      await page.waitForSelector('div'); // do a round trip
584      expect(divRemoved).toBe(false);
585      await page.evaluate(() => document.querySelector('div').remove());
586      expect(await waitForSelector).toBe(true);
587      expect(divRemoved).toBe(true);
588    });
589    it('should return null if waiting to hide non-existing element', async () => {
590      const { page } = getTestState();
591
592      const handle = await page.waitForSelector('non-existing', {
593        hidden: true,
594      });
595      expect(handle).toBe(null);
596    });
597    it('should respect timeout', async () => {
598      const { page, puppeteer } = getTestState();
599
600      let error = null;
601      await page
602        .waitForSelector('div', { timeout: 10 })
603        .catch((error_) => (error = error_));
604      expect(error).toBeTruthy();
605      expect(error.message).toContain(
606        'waiting for selector `div` failed: timeout'
607      );
608      expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
609    });
610    it('should have an error message specifically for awaiting an element to be hidden', async () => {
611      const { page } = getTestState();
612
613      await page.setContent(`<div></div>`);
614      let error = null;
615      await page
616        .waitForSelector('div', { hidden: true, timeout: 10 })
617        .catch((error_) => (error = error_));
618      expect(error).toBeTruthy();
619      expect(error.message).toContain(
620        'waiting for selector `div` to be hidden failed: timeout'
621      );
622    });
623
624    it('should respond to node attribute mutation', async () => {
625      const { page } = getTestState();
626
627      let divFound = false;
628      const waitForSelector = page
629        .waitForSelector('.zombo')
630        .then(() => (divFound = true));
631      await page.setContent(`<div class='notZombo'></div>`);
632      expect(divFound).toBe(false);
633      await page.evaluate(
634        () => (document.querySelector('div').className = 'zombo')
635      );
636      expect(await waitForSelector).toBe(true);
637    });
638    it('should return the element handle', async () => {
639      const { page } = getTestState();
640
641      const waitForSelector = page.waitForSelector('.zombo');
642      await page.setContent(`<div class='zombo'>anything</div>`);
643      expect(
644        await page.evaluate(
645          (x: HTMLElement) => x.textContent,
646          await waitForSelector
647        )
648      ).toBe('anything');
649    });
650    it('should have correct stack trace for timeout', async () => {
651      const { page } = getTestState();
652
653      let error;
654      await page
655        .waitForSelector('.zombo', { timeout: 10 })
656        .catch((error_) => (error = error_));
657      expect(error.stack).toContain('waiting for selector `.zombo` failed');
658      // The extension is ts here as Mocha maps back via sourcemaps.
659      expect(error.stack).toContain('waittask.spec.ts');
660    });
661  });
662
663  describe('Frame.waitForXPath', function () {
664    const addElement = (tag) =>
665      document.body.appendChild(document.createElement(tag));
666
667    it('should support some fancy xpath', async () => {
668      const { page } = getTestState();
669
670      await page.setContent(`<p>red herring</p><p>hello  world  </p>`);
671      const waitForXPath = page.waitForXPath(
672        '//p[normalize-space(.)="hello world"]'
673      );
674      expect(
675        await page.evaluate(
676          (x: HTMLElement) => x.textContent,
677          await waitForXPath
678        )
679      ).toBe('hello  world  ');
680    });
681    it('should respect timeout', async () => {
682      const { page, puppeteer } = getTestState();
683
684      let error = null;
685      await page
686        .waitForXPath('//div', { timeout: 10 })
687        .catch((error_) => (error = error_));
688      expect(error).toBeTruthy();
689      expect(error.message).toContain(
690        'waiting for XPath `//div` failed: timeout'
691      );
692      expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
693    });
694    it('should run in specified frame', async () => {
695      const { page, server } = getTestState();
696
697      await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
698      await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
699      const frame1 = page.frames()[1];
700      const frame2 = page.frames()[2];
701      const waitForXPathPromise = frame2.waitForXPath('//div');
702      await frame1.evaluate(addElement, 'div');
703      await frame2.evaluate(addElement, 'div');
704      const eHandle = await waitForXPathPromise;
705      expect(eHandle.executionContext().frame()).toBe(frame2);
706    });
707    it('should throw when frame is detached', async () => {
708      const { page, server } = getTestState();
709
710      await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
711      const frame = page.frames()[1];
712      let waitError = null;
713      const waitPromise = frame
714        .waitForXPath('//*[@class="box"]')
715        .catch((error) => (waitError = error));
716      await utils.detachFrame(page, 'frame1');
717      await waitPromise;
718      expect(waitError).toBeTruthy();
719      expect(waitError.message).toContain(
720        'waitForFunction failed: frame got detached.'
721      );
722    });
723    it('hidden should wait for display: none', async () => {
724      const { page } = getTestState();
725
726      let divHidden = false;
727      await page.setContent(`<div style='display: block;'></div>`);
728      const waitForXPath = page
729        .waitForXPath('//div', { hidden: true })
730        .then(() => (divHidden = true));
731      await page.waitForXPath('//div'); // do a round trip
732      expect(divHidden).toBe(false);
733      await page.evaluate(() =>
734        document.querySelector('div').style.setProperty('display', 'none')
735      );
736      expect(await waitForXPath).toBe(true);
737      expect(divHidden).toBe(true);
738    });
739    it('should return the element handle', async () => {
740      const { page } = getTestState();
741
742      const waitForXPath = page.waitForXPath('//*[@class="zombo"]');
743      await page.setContent(`<div class='zombo'>anything</div>`);
744      expect(
745        await page.evaluate(
746          (x: HTMLElement) => x.textContent,
747          await waitForXPath
748        )
749      ).toBe('anything');
750    });
751    it('should allow you to select a text node', async () => {
752      const { page } = getTestState();
753
754      await page.setContent(`<div>some text</div>`);
755      const text = await page.waitForXPath('//div/text()');
756      expect(await (await text.getProperty('nodeType')).jsonValue()).toBe(
757        3 /* Node.TEXT_NODE */
758      );
759    });
760    it('should allow you to select an element with single slash', async () => {
761      const { page } = getTestState();
762
763      await page.setContent(`<div>some text</div>`);
764      const waitForXPath = page.waitForXPath('/html/body/div');
765      expect(
766        await page.evaluate(
767          (x: HTMLElement) => x.textContent,
768          await waitForXPath
769        )
770      ).toBe('some text');
771    });
772  });
773});
774