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