diff options
Diffstat (limited to 'remote/test/puppeteer/test/src')
33 files changed, 1037 insertions, 564 deletions
diff --git a/remote/test/puppeteer/test/src/ariaqueryhandler.spec.ts b/remote/test/puppeteer/test/src/ariaqueryhandler.spec.ts index 434d01426a..0ffb8ae6a5 100644 --- a/remote/test/puppeteer/test/src/ariaqueryhandler.spec.ts +++ b/remote/test/puppeteer/test/src/ariaqueryhandler.spec.ts @@ -370,9 +370,10 @@ describe('AriaQueryHandler', () => { await detachFrame(page, 'frame1'); await waitPromise; expect(waitError).toBeTruthy(); - expect(waitError.message).toContain( - 'waitForFunction failed: frame got detached.' - ); + expect(waitError.message).atLeastOneToContain([ + 'waitForFunction failed: frame got detached.', + 'Browsing context already closed.', + ]); }); it('should survive cross-process navigation', async () => { diff --git a/remote/test/puppeteer/test/src/browser.spec.ts b/remote/test/puppeteer/test/src/browser.spec.ts index 6f21af5d9a..b8e0c8bb07 100644 --- a/remote/test/puppeteer/test/src/browser.spec.ts +++ b/remote/test/puppeteer/test/src/browser.spec.ts @@ -52,7 +52,9 @@ describe('Browser specs', function () { expect(process!.pid).toBeGreaterThan(0); }); it('should not return child_process for remote browser', async () => { - const {browser, puppeteer} = await getTestState(); + const {browser, puppeteer} = await getTestState({ + skipContextCreation: true, + }); const browserWSEndpoint = browser.wsEndpoint(); const remoteBrowser = await puppeteer.connect({ @@ -66,7 +68,9 @@ describe('Browser specs', function () { describe('Browser.isConnected', () => { it('should set the browser connected state', async () => { - const {browser, puppeteer} = await getTestState(); + const {browser, puppeteer} = await getTestState({ + skipContextCreation: true, + }); const browserWSEndpoint = browser.wsEndpoint(); const newBrowser = await puppeteer.connect({ diff --git a/remote/test/puppeteer/test/src/browsercontext.spec.ts b/remote/test/puppeteer/test/src/browsercontext.spec.ts index 9cbbda60a4..27709e90bf 100644 --- a/remote/test/puppeteer/test/src/browsercontext.spec.ts +++ b/remote/test/puppeteer/test/src/browsercontext.spec.ts @@ -18,9 +18,13 @@ describe('BrowserContext', function () { const {browser} = await getTestState({ skipContextCreation: true, }); - expect(browser.browserContexts()).toHaveLength(1); - const defaultContext = browser.browserContexts()[0]!; - expect(defaultContext!.isIncognito()).toBe(false); + + expect(browser.browserContexts().length).toBeGreaterThanOrEqual(1); + const defaultContext = browser.browserContexts().find(context => { + return !context.isIncognito(); + }); + expect(defaultContext).toBeDefined(); + let error!: Error; await defaultContext!.close().catch(error_ => { return (error = error_); @@ -33,13 +37,14 @@ describe('BrowserContext', function () { skipContextCreation: true, }); - expect(browser.browserContexts()).toHaveLength(1); - const context = await browser.createIncognitoBrowserContext(); + const contextCount = browser.browserContexts().length; + expect(contextCount).toBeGreaterThanOrEqual(1); + const context = await browser.createBrowserContext(); expect(context.isIncognito()).toBe(true); - expect(browser.browserContexts()).toHaveLength(2); + expect(browser.browserContexts()).toHaveLength(contextCount + 1); expect(browser.browserContexts().indexOf(context) !== -1).toBe(true); await context.close(); - expect(browser.browserContexts()).toHaveLength(1); + expect(browser.browserContexts()).toHaveLength(contextCount); }); it('should close all belonging targets once closing context', async () => { const {browser} = await getTestState({ @@ -48,7 +53,7 @@ describe('BrowserContext', function () { expect(await browser.pages()).toHaveLength(1); - const context = await browser.createIncognitoBrowserContext(); + const context = await browser.createBrowserContext(); await context.newPage(); expect(await browser.pages()).toHaveLength(2); expect(await context.pages()).toHaveLength(1); @@ -128,7 +133,7 @@ describe('BrowserContext', function () { it('should timeout waiting for a non-existent target', async () => { const {browser, server} = await getTestState(); - const context = await browser.createIncognitoBrowserContext(); + const context = await browser.createBrowserContext(); const error = await context .waitForTarget( target => { @@ -151,8 +156,8 @@ describe('BrowserContext', function () { }); // Create two incognito contexts. - const context1 = await browser.createIncognitoBrowserContext(); - const context2 = await browser.createIncognitoBrowserContext(); + const context1 = await browser.createBrowserContext(); + const context2 = await browser.createBrowserContext(); expect(context1.targets()).toHaveLength(0); expect(context2.targets()).toHaveLength(0); @@ -176,9 +181,9 @@ describe('BrowserContext', function () { }); expect(context1.targets()).toHaveLength(1); - expect(context1.targets()[0]).toBe(page1.target()); + expect(await context1.targets()[0]?.page()).toBe(page1); expect(context2.targets()).toHaveLength(1); - expect(context2.targets()[0]).toBe(page2.target()); + expect(await context2.targets()[0]?.page()).toBe(page2); // Make sure pages don't share localstorage or cookies. expect( @@ -213,16 +218,19 @@ describe('BrowserContext', function () { }); expect(browser.browserContexts()).toHaveLength(1); - const context = await browser.createIncognitoBrowserContext(); - expect(browser.browserContexts()).toHaveLength(2); - const remoteBrowser = await puppeteer.connect({ - browserWSEndpoint: browser.wsEndpoint(), - protocol: browser.protocol, - }); - const contexts = remoteBrowser.browserContexts(); - expect(contexts).toHaveLength(2); - await remoteBrowser.disconnect(); - await context.close(); + const context = await browser.createBrowserContext(); + try { + expect(browser.browserContexts()).toHaveLength(2); + const remoteBrowser = await puppeteer.connect({ + browserWSEndpoint: browser.wsEndpoint(), + protocol: browser.protocol, + }); + const contexts = remoteBrowser.browserContexts(); + expect(contexts).toHaveLength(2); + await remoteBrowser.disconnect(); + } finally { + await context.close(); + } }); it('should provide a context id', async () => { @@ -233,7 +241,7 @@ describe('BrowserContext', function () { expect(browser.browserContexts()).toHaveLength(1); expect(browser.browserContexts()[0]!.id).toBeUndefined(); - const context = await browser.createIncognitoBrowserContext(); + const context = await browser.createBrowserContext(); expect(browser.browserContexts()).toHaveLength(2); expect(browser.browserContexts()[1]!.id).toBeDefined(); await context.close(); @@ -333,7 +341,7 @@ describe('BrowserContext', function () { const {page, server, context, browser} = await getTestState(); await page.goto(server.EMPTY_PAGE); - const otherContext = await browser.createIncognitoBrowserContext(); + const otherContext = await browser.createBrowserContext(); const otherPage = await otherContext.newPage(); await otherPage.goto(server.EMPTY_PAGE); expect(await getPermission(page, 'geolocation')).toBe('prompt'); diff --git a/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts b/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts index 2000c0e435..887152f097 100644 --- a/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts +++ b/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts @@ -30,7 +30,7 @@ describe('Target.createCDPSession', function () { }); it('should not report created targets for custom CDP sessions', async () => { - const {browser} = await getTestState(); + const {context} = await getTestState(); let called = 0; const handler = async (target: Target) => { called++; @@ -39,9 +39,9 @@ describe('Target.createCDPSession', function () { } await target.createCDPSession(); }; - browser.browserContexts()[0]!.on('targetcreated', handler); - await browser.newPage(); - browser.browserContexts()[0]!.off('targetcreated', handler); + context.on('targetcreated', handler); + await context.newPage(); + context.off('targetcreated', handler); }); it('should send events', async () => { diff --git a/remote/test/puppeteer/test/src/cdp/devtools.spec.ts b/remote/test/puppeteer/test/src/cdp/devtools.spec.ts index c158481af2..c48b4c353b 100644 --- a/remote/test/puppeteer/test/src/cdp/devtools.spec.ts +++ b/remote/test/puppeteer/test/src/cdp/devtools.spec.ts @@ -93,7 +93,7 @@ describe('DevTools', function () { const browser = await launchBrowser( Object.assign({devtools: true}, launchOptions) ); - const context = await browser.createIncognitoBrowserContext(); + const context = await browser.createBrowserContext(); await Promise.all([ context.newPage(), browser.waitForTarget((target: {url: () => string | string[]}) => { @@ -106,7 +106,7 @@ describe('DevTools', function () { const browser = await launchBrowser( Object.assign({devtools: true}, launchOptions) ); - const context = await browser.createIncognitoBrowserContext(); + const context = await browser.createBrowserContext(); const [target] = await Promise.all([ browser.waitForTarget((target: {url: () => string | string[]}) => { return target.url().includes('devtools://'); diff --git a/remote/test/puppeteer/test/src/cdp/pdf.spec.ts b/remote/test/puppeteer/test/src/cdp/pdf.spec.ts new file mode 100644 index 0000000000..06a41de36f --- /dev/null +++ b/remote/test/puppeteer/test/src/cdp/pdf.spec.ts @@ -0,0 +1,55 @@ +/** + * @license + * Copyright 2017 Google Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import {readFile, unlink} from 'fs/promises'; + +import expect from 'expect'; + +import {getTestState, setupTestBrowserHooks} from '../mocha-utils.js'; + +describe('Page.pdf', () => { + setupTestBrowserHooks(); + + it('can print to PDF with accessible', async () => { + const {page, server} = await getTestState(); + + const outputFile = __dirname + '/../../assets/output.pdf'; + const outputFileAccessible = + __dirname + '/../../assets/output-accessible.pdf'; + await page.goto(server.PREFIX + '/pdf.html'); + await page.pdf({path: outputFile, tagged: false}); + await page.pdf({path: outputFileAccessible, tagged: true}); + try { + const [base, tagged] = await Promise.all([ + readFile(outputFile), + readFile(outputFileAccessible), + ]); + expect(tagged.byteLength).toBeGreaterThan(base.byteLength); + } finally { + await Promise.all([unlink(outputFile), unlink(outputFileAccessible)]); + } + }); + + it('can print to PDF with outline', async () => { + const {page, server} = await getTestState(); + + const outputFile = __dirname + '/../../assets/output.pdf'; + const outputFileOutlined = __dirname + '/../../assets/output-outlined.pdf'; + await page.goto(server.PREFIX + '/pdf.html'); + await page.pdf({path: outputFile, tagged: true}); + await page.pdf({path: outputFileOutlined, tagged: true, outline: true}); + try { + const [base, outlined] = await Promise.all([ + readFile(outputFile), + readFile(outputFileOutlined), + ]); + + expect(outlined.byteLength).toBeGreaterThan(base.byteLength); + } finally { + await Promise.all([unlink(outputFile), unlink(outputFileOutlined)]); + } + }); +}); diff --git a/remote/test/puppeteer/test/src/screencast.spec.ts b/remote/test/puppeteer/test/src/cdp/screencast.spec.ts index b645f55da7..2833ff4d67 100644 --- a/remote/test/puppeteer/test/src/screencast.spec.ts +++ b/remote/test/puppeteer/test/src/cdp/screencast.spec.ts @@ -8,8 +8,8 @@ import {statSync} from 'fs'; import expect from 'expect'; -import {getTestState, setupTestBrowserHooks} from './mocha-utils.js'; -import {getUniqueVideoFilePlaceholder} from './utils.js'; +import {getTestState, setupTestBrowserHooks} from '../mocha-utils.js'; +import {getUniqueVideoFilePlaceholder} from '../utils.js'; describe('Screencasts', function () { setupTestBrowserHooks(); diff --git a/remote/test/puppeteer/test/src/cookies.spec.ts b/remote/test/puppeteer/test/src/cookies.spec.ts index f232831b72..1fa4a9407c 100644 --- a/remote/test/puppeteer/test/src/cookies.spec.ts +++ b/remote/test/puppeteer/test/src/cookies.spec.ts @@ -150,9 +150,7 @@ describe('Cookie specs', () => { expires: -1, size: 11, httpOnly: false, - secure: true, session: true, - sourcePort: 443, sourceScheme: 'Secure', }, { @@ -164,13 +162,47 @@ describe('Cookie specs', () => { expires: -1, size: 10, httpOnly: false, - secure: true, session: true, - sourcePort: 443, sourceScheme: 'Secure', }, ]); }); + it('should not get cookies from subdomain', async () => { + const {page} = await getTestState(); + await page.setCookie({ + url: 'https://base_domain.com', + name: 'doggo', + value: 'woofs', + }); + const cookies = await page.cookies('https://sub_domain.base_domain.com'); + expect(cookies).toHaveLength(0); + }); + it('should get cookies from nested path', async () => { + const {page} = await getTestState(); + await page.setCookie({ + url: 'https://foo.com', + path: '/some_path', + name: 'doggo', + value: 'woofs', + }); + const cookies = await page.cookies( + 'https://foo.com/some_path/nested_path' + ); + expect(cookies).toHaveLength(1); + }); + it('should not get cookies from not nested path', async () => { + const {page} = await getTestState(); + await page.setCookie({ + url: 'https://foo.com', + path: '/some_path', + name: 'doggo', + value: 'woofs', + }); + const cookies = await page.cookies( + 'https://foo.com/some_path_looks_like_nested' + ); + expect(cookies).toHaveLength(0); + }); }); describe('Page.setCookie', function () { it('should work', async () => { @@ -190,24 +222,27 @@ describe('Cookie specs', () => { it('should isolate cookies in browser contexts', async () => { const {page, server, browser} = await getTestState(); - const anotherContext = await browser.createIncognitoBrowserContext(); - const anotherPage = await anotherContext.newPage(); + const anotherContext = await browser.createBrowserContext(); + try { + const anotherPage = await anotherContext.newPage(); - await page.goto(server.EMPTY_PAGE); - await anotherPage.goto(server.EMPTY_PAGE); - - await page.setCookie({name: 'page1cookie', value: 'page1value'}); - await anotherPage.setCookie({name: 'page2cookie', value: 'page2value'}); - - const cookies1 = await page.cookies(); - const cookies2 = await anotherPage.cookies(); - expect(cookies1).toHaveLength(1); - expect(cookies2).toHaveLength(1); - expect(cookies1[0]!.name).toBe('page1cookie'); - expect(cookies1[0]!.value).toBe('page1value'); - expect(cookies2[0]!.name).toBe('page2cookie'); - expect(cookies2[0]!.value).toBe('page2value'); - await anotherContext.close(); + await page.goto(server.EMPTY_PAGE); + await anotherPage.goto(server.EMPTY_PAGE); + + await page.setCookie({name: 'page1cookie', value: 'page1value'}); + await anotherPage.setCookie({name: 'page2cookie', value: 'page2value'}); + + const cookies1 = await page.cookies(); + const cookies2 = await anotherPage.cookies(); + expect(cookies1).toHaveLength(1); + expect(cookies2).toHaveLength(1); + expect(cookies1[0]!.name).toBe('page1cookie'); + expect(cookies1[0]!.value).toBe('page1value'); + expect(cookies2[0]!.name).toBe('page2cookie'); + expect(cookies2[0]!.value).toBe('page2value'); + } finally { + await anotherContext.close(); + } }); it('should set multiple cookies', async () => { const {page, server} = await getTestState(); @@ -271,7 +306,6 @@ describe('Cookie specs', () => { httpOnly: false, secure: false, session: true, - sourcePort: 80, sourceScheme: 'NonSecure', }, ] @@ -298,7 +332,6 @@ describe('Cookie specs', () => { httpOnly: false, secure: false, session: true, - sourcePort: 80, sourceScheme: 'NonSecure', }, ]); @@ -401,9 +434,7 @@ describe('Cookie specs', () => { expires: -1, size: 18, httpOnly: false, - secure: true, session: true, - sourcePort: 443, sourceScheme: 'Secure', }, ]); @@ -446,7 +477,6 @@ describe('Cookie specs', () => { httpOnly: false, secure: false, session: true, - sourcePort: 80, sourceScheme: 'NonSecure', }, ]); @@ -465,7 +495,6 @@ describe('Cookie specs', () => { httpOnly: false, secure: false, session: true, - sourcePort: 80, sourceScheme: 'NonSecure', }, ] @@ -515,7 +544,6 @@ describe('Cookie specs', () => { sameSite: 'None', secure: true, session: true, - sourcePort: 443, sourceScheme: 'Secure', }, ] @@ -527,7 +555,7 @@ describe('Cookie specs', () => { }); describe('Page.deleteCookie', function () { - it('should work', async () => { + it('should delete cookie', async () => { const {page, server} = await getTestState(); await page.goto(server.EMPTY_PAGE); @@ -553,5 +581,139 @@ describe('Cookie specs', () => { 'cookie1=1; cookie3=3' ); }); + it('should not delete cookie for different domain', async () => { + const {page, server} = await getTestState(); + const COOKIE_DESTINATION_URL = 'https://example.com'; + const COOKIE_NAME = 'some_cookie_name'; + + await page.goto(server.EMPTY_PAGE); + // Set a cookie for the current page. + await page.setCookie({ + name: COOKIE_NAME, + value: 'local page cookie value', + }); + expect(await page.cookies()).toHaveLength(1); + + // Set a cookie for different domain. + await page.setCookie({ + url: COOKIE_DESTINATION_URL, + name: COOKIE_NAME, + value: 'COOKIE_DESTINATION_URL cookie value', + }); + expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(1); + + await page.deleteCookie({name: COOKIE_NAME}); + + // Verify the cookie is deleted for the current page. + expect(await page.cookies()).toHaveLength(0); + + // Verify the cookie is not deleted for different domain. + await expectCookieEquals(await page.cookies(COOKIE_DESTINATION_URL), [ + { + name: COOKIE_NAME, + value: 'COOKIE_DESTINATION_URL cookie value', + domain: 'example.com', + path: '/', + sameParty: false, + expires: -1, + size: 51, + httpOnly: false, + secure: true, + session: true, + sourceScheme: 'Secure', + }, + ]); + }); + it('should delete cookie for specified URL', async () => { + const {page, server} = await getTestState(); + const COOKIE_DESTINATION_URL = 'https://example.com'; + const COOKIE_NAME = 'some_cookie_name'; + + await page.goto(server.EMPTY_PAGE); + // Set a cookie for the current page. + await page.setCookie({ + name: COOKIE_NAME, + value: 'some_cookie_value', + }); + expect(await page.cookies()).toHaveLength(1); + + // Set a cookie for specified URL. + await page.setCookie({ + url: COOKIE_DESTINATION_URL, + name: COOKIE_NAME, + value: 'another_cookie_value', + }); + expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(1); + + // Delete the cookie for specified URL. + await page.deleteCookie({ + url: COOKIE_DESTINATION_URL, + name: COOKIE_NAME, + }); + + // Verify the cookie is deleted for specified URL. + expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(0); + + // Verify the cookie is not deleted for the current page. + await expectCookieEquals(await page.cookies(), [ + { + name: COOKIE_NAME, + value: 'some_cookie_value', + domain: 'localhost', + path: '/', + sameParty: false, + expires: -1, + size: 33, + httpOnly: false, + secure: false, + session: true, + sourceScheme: 'NonSecure', + }, + ]); + }); + it('should delete cookie for specified URL regardless of the current page', async () => { + // This test verifies the page.deleteCookie method deletes cookies for the custom + // destination URL, even if it was set from another page. Depending on the cookie + // partitioning implementation, this test case does not pass, if source origin is in + // the default cookie partition. + + const {page, server} = await getTestState(); + const COOKIE_DESTINATION_URL = 'https://example.com'; + const COOKIE_NAME = 'some_cookie_name'; + const URL_1 = server.EMPTY_PAGE; + const URL_2 = server.CROSS_PROCESS_PREFIX + '/empty.html'; + + await page.goto(URL_1); + // Set a cookie for the COOKIE_DESTINATION from URL_1. + await page.setCookie({ + url: COOKIE_DESTINATION_URL, + name: COOKIE_NAME, + value: 'Cookie from URL_1', + }); + expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(1); + + await page.goto(URL_2); + // Set a cookie for the COOKIE_DESTINATION from URL_2. + await page.setCookie({ + url: COOKIE_DESTINATION_URL, + name: COOKIE_NAME, + value: 'Cookie from URL_2', + }); + expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(1); + + // Delete the cookie for the COOKIE_DESTINATION from URL_2. + await page.deleteCookie({ + name: COOKIE_NAME, + url: COOKIE_DESTINATION_URL, + }); + + // Expect the cookie for the COOKIE_DESTINATION from URL_2 is deleted. + expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(0); + + // Navigate back to the URL_1. + await page.goto(server.EMPTY_PAGE); + // Expect the cookie for the COOKIE_DESTINATION from URL_1 is deleted. + expect(await page.cookies(COOKIE_DESTINATION_URL)).toHaveLength(0); + }); }); }); diff --git a/remote/test/puppeteer/test/src/coverage.spec.ts b/remote/test/puppeteer/test/src/coverage.spec.ts index 6a95db541c..612d634007 100644 --- a/remote/test/puppeteer/test/src/coverage.spec.ts +++ b/remote/test/puppeteer/test/src/coverage.spec.ts @@ -49,12 +49,11 @@ describe('Coverage specs', function () { await page.coverage.startJSCoverage({reportAnonymousScripts: true}); await page.goto(server.PREFIX + '/jscoverage/eval.html'); const coverage = await page.coverage.stopJSCoverage(); - expect( - coverage.find(entry => { - return entry.url.startsWith('debugger://'); - }) - ).not.toBe(null); - expect(coverage).toHaveLength(2); + + const filtered = coverage.filter(entry => { + return !entry.url.startsWith('debugger://'); + }); + expect(filtered).toHaveLength(1); }); it('should ignore pptr internal scripts if reportAnonymousScripts is true', async () => { const {page, server} = await getTestState(); diff --git a/remote/test/puppeteer/test/src/debugInfo.spec.ts b/remote/test/puppeteer/test/src/debugInfo.spec.ts index 079107cab7..4f79231667 100644 --- a/remote/test/puppeteer/test/src/debugInfo.spec.ts +++ b/remote/test/puppeteer/test/src/debugInfo.spec.ts @@ -15,6 +15,18 @@ describe('DebugInfo', function () { it('should work', async () => { const {page, browser} = await getTestState(); + for (let i = 0; i < 5; i++) { + if (!browser.debugInfo.pendingProtocolErrors.length) { + break; + } + await new Promise(resolve => { + return setTimeout(resolve, 200); + }); + } + + // Insure that the previous test are flushed + expect(browser.debugInfo.pendingProtocolErrors).toHaveLength(0); + const promise = page.evaluate(() => { return new Promise(resolve => { // @ts-expect-error another context diff --git a/remote/test/puppeteer/test/src/defaultbrowsercontext.spec.ts b/remote/test/puppeteer/test/src/defaultbrowsercontext.spec.ts index 69a5a069af..662c6826cb 100644 --- a/remote/test/puppeteer/test/src/defaultbrowsercontext.spec.ts +++ b/remote/test/puppeteer/test/src/defaultbrowsercontext.spec.ts @@ -62,7 +62,6 @@ describe('DefaultBrowserContext', function () { httpOnly: false, secure: false, session: true, - sourcePort: 80, sourceScheme: 'NonSecure', }, ]); @@ -96,7 +95,6 @@ describe('DefaultBrowserContext', function () { httpOnly: false, secure: false, session: true, - sourcePort: 80, sourceScheme: 'NonSecure', }, ]); diff --git a/remote/test/puppeteer/test/src/device-request-prompt.spec.ts b/remote/test/puppeteer/test/src/device-request-prompt.spec.ts index e6e2cdd65e..450a8d800c 100644 --- a/remote/test/puppeteer/test/src/device-request-prompt.spec.ts +++ b/remote/test/puppeteer/test/src/device-request-prompt.spec.ts @@ -28,7 +28,7 @@ describe('device request prompt', function () { }); beforeEach(async () => { - state.context = await state.browser.createIncognitoBrowserContext(); + state.context = await state.browser.createBrowserContext(); state.page = await state.context.newPage(); }); diff --git a/remote/test/puppeteer/test/src/elementhandle.spec.ts b/remote/test/puppeteer/test/src/elementhandle.spec.ts index 9aaf914224..e0f1e41878 100644 --- a/remote/test/puppeteer/test/src/elementhandle.spec.ts +++ b/remote/test/puppeteer/test/src/elementhandle.spec.ts @@ -34,18 +34,14 @@ describe('ElementHandle specs', function () { expect(box).toEqual({x: 100, y: 50, width: 50, height: 50}); }); it('should handle nested frames', async () => { - const {page, server, isChrome} = await getTestState(); + const {page, server} = await getTestState(); await page.setViewport({width: 500, height: 500}); await page.goto(server.PREFIX + '/frames/nested-frames.html'); const nestedFrame = page.frames()[1]!.childFrames()[1]!; using elementHandle = (await nestedFrame.$('div'))!; const box = await elementHandle.boundingBox(); - if (isChrome) { - expect(box).toEqual({x: 28, y: 182, width: 264, height: 18}); - } else { - expect(box).toEqual({x: 28, y: 182, width: 254, height: 18}); - } + expect(box).toEqual({x: 28, y: 182, width: 300, height: 18}); }); it('should return null for invisible elements', async () => { const {page} = await getTestState(); @@ -472,10 +468,8 @@ describe('ElementHandle specs', function () { }) ).toStrictEqual('bar1'); }); - }); - describe('Element.waitForXPath', () => { - it('should wait correctly with waitForXPath on an element', async () => { + it('should wait correctly with waitForSelector and xpath on an element', async () => { const {page} = await getTestState(); // Set the page content after the waitFor has been started. await page.setContent( @@ -490,20 +484,18 @@ describe('ElementHandle specs', function () { </div>` ); - using el1 = (await page.waitForSelector( + using elById = (await page.waitForSelector( '#el1' )) as ElementHandle<HTMLDivElement>; - for (const path of ['//div', './/div']) { - using e = (await el1.waitForXPath( - path - )) as ElementHandle<HTMLDivElement>; - expect( - await e.evaluate(el => { - return el.id; - }) - ).toStrictEqual('el2'); - } + using elByXpath = (await elById.waitForSelector( + 'xpath/.//div' + )) as ElementHandle<HTMLDivElement>; + expect( + await elByXpath.evaluate(el => { + return el.id; + }) + ).toStrictEqual('el2'); }); }); diff --git a/remote/test/puppeteer/test/src/evaluation.spec.ts b/remote/test/puppeteer/test/src/evaluation.spec.ts index 3305b59cc2..88cccb82dd 100644 --- a/remote/test/puppeteer/test/src/evaluation.spec.ts +++ b/remote/test/puppeteer/test/src/evaluation.spec.ts @@ -408,9 +408,10 @@ describe('Evaluation specs', function () { return (error = error_); }); expect(error).toBeTruthy(); - expect(error.message).toContain( - 'JSHandles can be evaluated only in the context they were created' - ); + expect(error.message).atLeastOneToContain([ + 'JSHandles can be evaluated only in the context they were created', + "Trying to evaluate JSHandle from different frames. Usually this means you're using a handle from a page on a different page.", + ]); }); it('should simulate a user gesture', async () => { const {page} = await getTestState(); diff --git a/remote/test/puppeteer/test/src/fixtures.spec.ts b/remote/test/puppeteer/test/src/fixtures.spec.ts index ca11e94cac..e7a2e1ac9b 100644 --- a/remote/test/puppeteer/test/src/fixtures.spec.ts +++ b/remote/test/puppeteer/test/src/fixtures.spec.ts @@ -18,7 +18,7 @@ describe('Fixtures', function () { it('dumpio option should work with pipe option', async () => { const {defaultBrowserOptions, puppeteerPath, headless} = await getTestState(); - if (headless !== 'true') { + if (headless !== 'shell') { // This test only works in the old headless mode. return; } @@ -42,7 +42,8 @@ describe('Fixtures', function () { expect(dumpioData).toContain('message from dumpio'); }); it('should dump browser process stderr', async () => { - const {defaultBrowserOptions, puppeteerPath} = await getTestState(); + const {defaultBrowserOptions, isFirefox, puppeteerPath} = + await getTestState(); let dumpioData = ''; const options = Object.assign({}, defaultBrowserOptions, {dumpio: true}); @@ -57,7 +58,11 @@ describe('Fixtures', function () { await new Promise(resolve => { return res.on('close', resolve); }); - expect(dumpioData).toContain('DevTools listening on ws://'); + if (isFirefox && defaultBrowserOptions.protocol === 'webDriverBiDi') { + expect(dumpioData).toContain('WebDriver BiDi listening on ws://'); + } else { + expect(dumpioData).toContain('DevTools listening on ws://'); + } }); it('should close the browser when the node process closes', async () => { const {defaultBrowserOptions, puppeteerPath, puppeteer} = diff --git a/remote/test/puppeteer/test/src/frame.spec.ts b/remote/test/puppeteer/test/src/frame.spec.ts index 3b2456821a..a49fb19482 100644 --- a/remote/test/puppeteer/test/src/frame.spec.ts +++ b/remote/test/puppeteer/test/src/frame.spec.ts @@ -205,6 +205,18 @@ describe('Frame specs', function () { expect(detachedFrames).toHaveLength(4); expect(navigatedFrames).toHaveLength(1); }); + + it('should click elements in a frameset', async () => { + const {page, server} = await getTestState(); + await page.goto(server.PREFIX + '/frames/frameset.html'); + const frame = await page.waitForFrame(frame => { + return frame.url().endsWith('/frames/frame.html'); + }); + using div = await frame.waitForSelector('div'); + expect(div).toBeTruthy(); + await div?.click(); + }); + it('should report frame from-inside shadow DOM', async () => { const {page, server} = await getTestState(); diff --git a/remote/test/puppeteer/test/src/headful.spec.ts b/remote/test/puppeteer/test/src/headful.spec.ts index 1e3248b4ff..67ae9f335e 100644 --- a/remote/test/puppeteer/test/src/headful.spec.ts +++ b/remote/test/puppeteer/test/src/headful.spec.ts @@ -12,18 +12,18 @@ import expect from 'expect'; import type {PuppeteerLaunchOptions} from 'puppeteer-core/internal/node/PuppeteerNode.js'; import {rmSync} from 'puppeteer-core/internal/node/util/fs.js'; -import {getTestState, isHeadless, launch} from './mocha-utils.js'; +import {getTestState, launch} from './mocha-utils.js'; const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); -(!isHeadless ? describe : describe.skip)('headful tests', function () { +describe('headful tests', function () { /* These tests fire up an actual browser so let's * allow a higher timeout */ this.timeout(20_000); - let headfulOptions: PuppeteerLaunchOptions | undefined; - let headlessOptions: PuppeteerLaunchOptions & {headless: boolean}; + let headfulOptions: PuppeteerLaunchOptions & {headless: false}; + let headlessOptions: PuppeteerLaunchOptions & {headless: true}; const browsers: Array<() => Promise<void>> = []; @@ -32,10 +32,10 @@ const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); skipLaunch: true, }); headfulOptions = Object.assign({}, defaultBrowserOptions, { - headless: false, + headless: false as const, }); headlessOptions = Object.assign({}, defaultBrowserOptions, { - headless: true, + headless: true as const, }); }); @@ -64,23 +64,30 @@ const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); const headfulBrowser = await launchBrowser( Object.assign({userDataDir}, headfulOptions) ); - const headfulPage = await headfulBrowser.newPage(); - await headfulPage.goto(server.EMPTY_PAGE); - await headfulPage.evaluate(() => { - return (document.cookie = - 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'); - }); - await headfulBrowser.close(); + try { + const headfulPage = await headfulBrowser.newPage(); + await headfulPage.goto(server.EMPTY_PAGE); + await headfulPage.evaluate(() => { + return (document.cookie = + 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'); + }); + } finally { + await headfulBrowser.close(); + } // Read the cookie from headless chrome const headlessBrowser = await launchBrowser( Object.assign({userDataDir}, headlessOptions) ); - const headlessPage = await headlessBrowser.newPage(); - await headlessPage.goto(server.EMPTY_PAGE); - const cookie = await headlessPage.evaluate(() => { - return document.cookie; - }); - await headlessBrowser.close(); + let cookie = ''; + try { + const headlessPage = await headlessBrowser.newPage(); + await headlessPage.goto(server.EMPTY_PAGE); + cookie = await headlessPage.evaluate(() => { + return document.cookie; + }); + } finally { + await headlessBrowser.close(); + } // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 try { rmSync(userDataDir); diff --git a/remote/test/puppeteer/test/src/ignorehttpserrors.spec.ts b/remote/test/puppeteer/test/src/ignorehttpserrors.spec.ts index 8fb557cb88..d8e5603388 100644 --- a/remote/test/puppeteer/test/src/ignorehttpserrors.spec.ts +++ b/remote/test/puppeteer/test/src/ignorehttpserrors.spec.ts @@ -32,7 +32,7 @@ describe('ignoreHTTPSErrors', function () { }); beforeEach(async () => { - state.context = await state.browser.createIncognitoBrowserContext(); + state.context = await state.browser.createBrowserContext(); state.page = await state.context.newPage(); }); diff --git a/remote/test/puppeteer/test/src/input.spec.ts b/remote/test/puppeteer/test/src/input.spec.ts index 7e4cae6709..47064528d3 100644 --- a/remote/test/puppeteer/test/src/input.spec.ts +++ b/remote/test/puppeteer/test/src/input.spec.ts @@ -17,14 +17,13 @@ const FILE_TO_UPLOAD = path.join(__dirname, '/../assets/file-to-upload.txt'); describe('input tests', function () { setupTestBrowserHooks(); - describe('input', function () { + describe('ElementHandle.uploadFile', function () { it('should upload the file', async () => { const {page, server} = await getTestState(); await page.goto(server.PREFIX + '/input/fileupload.html'); - const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD); using input = (await page.$('input'))!; - await page.evaluate((e: HTMLElement) => { + await input.evaluate(e => { (globalThis as any)._inputEvents = []; e.addEventListener('change', ev => { return (globalThis as any)._inputEvents.push(ev.type); @@ -32,34 +31,63 @@ describe('input tests', function () { e.addEventListener('input', ev => { return (globalThis as any)._inputEvents.push(ev.type); }); - }, input); - await input.uploadFile(filePath); + }); + + const file = path.relative(process.cwd(), FILE_TO_UPLOAD); + await input.uploadFile(file); + expect( - await page.evaluate((e: HTMLInputElement) => { - return e.files![0]!.name; - }, input) + await input.evaluate(e => { + return e.files?.[0]?.name; + }) ).toBe('file-to-upload.txt'); expect( - await page.evaluate((e: HTMLInputElement) => { - return e.files![0]!.type; - }, input) + await input.evaluate(e => { + return e.files?.[0]?.type; + }) ).toBe('text/plain'); expect( await page.evaluate(() => { return (globalThis as any)._inputEvents; }) ).toEqual(['input', 'change']); + }); + + it('should read the file', async () => { + const {page, server} = await getTestState(); + + await page.goto(server.PREFIX + '/input/fileupload.html'); + using input = (await page.$('input'))!; + await input.evaluate(e => { + (globalThis as any)._inputEvents = []; + e.addEventListener('change', ev => { + return (globalThis as any)._inputEvents.push(ev.type); + }); + e.addEventListener('input', ev => { + return (globalThis as any)._inputEvents.push(ev.type); + }); + }); + + const file = path.relative(process.cwd(), FILE_TO_UPLOAD); + await input.uploadFile(file); + expect( - await page.evaluate((e: HTMLInputElement) => { + await input.evaluate(e => { + const file = e.files?.[0]; + if (!file) { + throw new Error('No file found'); + } + const reader = new FileReader(); const promise = new Promise(fulfill => { - return (reader.onload = fulfill); + reader.addEventListener('load', fulfill); }); - reader.readAsText(e.files![0]!); + reader.readAsText(file); + return promise.then(() => { return reader.result; }); - }, input) + }) ).toBe('contents of the file'); }); }); diff --git a/remote/test/puppeteer/test/src/launcher.spec.ts b/remote/test/puppeteer/test/src/launcher.spec.ts index f31b22b1e5..876f8d1624 100644 --- a/remote/test/puppeteer/test/src/launcher.spec.ts +++ b/remote/test/puppeteer/test/src/launcher.spec.ts @@ -16,7 +16,7 @@ import type {Page} from 'puppeteer-core/internal/api/Page.js'; import {rmSync} from 'puppeteer-core/internal/node/util/fs.js'; import sinon from 'sinon'; -import {getTestState, isHeadless, launch} from './mocha-utils.js'; +import {getTestState, launch} from './mocha-utils.js'; import {dumpFrames, waitEvent} from './utils.js'; const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); @@ -48,7 +48,10 @@ describe('Launcher specs', function () { [ 'Navigating frame was detached', 'Protocol error (Page.navigate): Target closed.', - ].includes(error.message) + 'Protocol error (browsingContext.navigate): Target closed', + ].some(message => { + return error.message.startsWith(message); + }) ).toBeTruthy(); } finally { await close(); @@ -99,6 +102,7 @@ describe('Launcher specs', function () { expect(message).atLeastOneToContain([ 'Target closed', 'Page closed!', + 'Browser already closed', ]); expect(message).not.toContain('Timeout'); } @@ -363,9 +367,9 @@ describe('Launcher specs', function () { if (isChrome) { expect(puppeteer.defaultArgs()).toContain('--no-first-run'); - expect(puppeteer.defaultArgs()).toContain('--headless'); + expect(puppeteer.defaultArgs()).toContain('--headless=new'); expect(puppeteer.defaultArgs({headless: false})).not.toContain( - '--headless' + '--headless=new' ); expect(puppeteer.defaultArgs({userDataDir: 'foo'})).toContain( `--user-data-dir=${path.resolve('foo')}` @@ -408,21 +412,18 @@ describe('Launcher specs', function () { expect(puppeteer.product).toBe('firefox'); } }); - (!isHeadless ? it : it.skip)( - 'should work with no default arguments', - async () => { - const {context, close} = await launch({ - ignoreDefaultArgs: true, - }); - try { - const page = await context.newPage(); - expect(await page.evaluate('11 * 11')).toBe(121); - await page.close(); - } finally { - await close(); - } + it('should work with no default arguments', async () => { + const {context, close} = await launch({ + ignoreDefaultArgs: true, + }); + try { + const page = await context.newPage(); + expect(await page.evaluate('11 * 11')).toBe(121); + await page.close(); + } finally { + await close(); } - ); + }); it('should filter out ignored default arguments in Chrome', async () => { const {defaultBrowserOptions, puppeteer} = await getTestState({ skipLaunch: true, @@ -590,31 +591,6 @@ describe('Launcher specs', function () { }); expect(error.message).toContain('either pipe or debugging port'); }); - (!isHeadless ? it : it.skip)( - 'should launch Chrome properly with --no-startup-window and waitForInitialPage=false', - async () => { - const {defaultBrowserOptions} = await getTestState({ - skipLaunch: true, - }); - const options = { - waitForInitialPage: false, - // This is needed to prevent Puppeteer from adding an initial blank page. - // See also https://github.com/puppeteer/puppeteer/blob/ad6b736039436fcc5c0a262e5b575aa041427be3/src/node/Launcher.ts#L200 - ignoreDefaultArgs: true, - ...defaultBrowserOptions, - args: ['--no-startup-window'], - }; - const {browser, close} = await launch(options, { - createContext: false, - }); - try { - const pages = await browser.pages(); - expect(pages).toHaveLength(0); - } finally { - await close(); - } - } - ); }); describe('Puppeteer.launch', function () { diff --git a/remote/test/puppeteer/test/src/mocha-utils.ts b/remote/test/puppeteer/test/src/mocha-utils.ts index 3fff9c9930..333204d83b 100644 --- a/remote/test/puppeteer/test/src/mocha-utils.ts +++ b/remote/test/puppeteer/test/src/mocha-utils.ts @@ -8,13 +8,13 @@ import fs from 'fs'; import path from 'path'; import {TestServer} from '@pptr/testserver'; -import type {Protocol} from 'devtools-protocol'; import expect from 'expect'; import type * as MochaBase from 'mocha'; import puppeteer from 'puppeteer/lib/cjs/puppeteer/puppeteer.js'; import type {Browser} from 'puppeteer-core/internal/api/Browser.js'; import type {BrowserContext} from 'puppeteer-core/internal/api/BrowserContext.js'; import type {Page} from 'puppeteer-core/internal/api/Page.js'; +import type {Cookie} from 'puppeteer-core/internal/common/Cookie.js'; import type { PuppeteerLaunchOptions, PuppeteerNode, @@ -68,8 +68,8 @@ const product = const headless = (process.env['HEADLESS'] || 'true').trim().toLowerCase() as | 'true' | 'false' - | 'new'; -export const isHeadless = headless === 'true' || headless === 'new'; + | 'shell'; +export const isHeadless = headless === 'true' || headless === 'shell'; const isFirefox = product === 'firefox'; const isChrome = product === 'chrome'; const protocol = (process.env['PUPPETEER_PROTOCOL'] || 'cdp') as @@ -93,7 +93,7 @@ const defaultBrowserOptions = Object.assign( { handleSIGINT: true, executablePath: process.env['BINARY'], - headless: headless === 'new' ? ('new' as const) : isHeadless, + headless: headless === 'shell' ? ('shell' as const) : isHeadless, dumpio: !!process.env['DUMPIO'], protocol, }, @@ -115,7 +115,7 @@ if (defaultBrowserOptions.executablePath) { const processVariables: { product: string; - headless: 'true' | 'false' | 'new'; + headless: 'true' | 'false' | 'shell'; isHeadless: boolean; isFirefox: boolean; isChrome: boolean; @@ -216,7 +216,7 @@ export const getTestState = async ( } if (!skipContextCreation) { - state.context = await state.browser!.createIncognitoBrowserContext(); + state.context = await state.browser!.createBrowserContext(); state.page = await state.context.newPage(); } return state as PuppeteerTestState; @@ -245,7 +245,7 @@ export interface PuppeteerTestState { isFirefox: boolean; isChrome: boolean; isHeadless: boolean; - headless: 'true' | 'false' | 'new'; + headless: 'true' | 'false' | 'shell'; puppeteerPath: string; } const state: Partial<PuppeteerTestState> = {}; @@ -263,7 +263,7 @@ if ( } -> mode: ${ processVariables.isHeadless - ? processVariables.headless === 'new' + ? processVariables.headless === 'true' ? '--headless=new' : '--headless' : 'headful' @@ -372,23 +372,27 @@ expect.extend({ }); export const expectCookieEquals = async ( - cookies: Protocol.Network.Cookie[], - expectedCookies: Array<Partial<Protocol.Network.Cookie>> + cookies: Cookie[], + expectedCookies: Array<Partial<Cookie>> ): Promise<void> => { if (!processVariables.isChrome) { // Only keep standard properties when testing on a browser other than Chrome. expectedCookies = expectedCookies.map(cookie => { - return { - domain: cookie.domain, - expires: cookie.expires, - httpOnly: cookie.httpOnly, - name: cookie.name, - path: cookie.path, - secure: cookie.secure, - session: cookie.session, - size: cookie.size, - value: cookie.value, - }; + return Object.fromEntries( + Object.entries(cookie).filter(([key]) => { + return [ + 'domain', + 'expires', + 'httpOnly', + 'name', + 'path', + 'secure', + 'session', + 'size', + 'value', + ].includes(key); + }) + ); }); } @@ -479,7 +483,7 @@ export const launch = async ( let context: BrowserContext; let page: Page; if (createContext) { - context = await browser.createIncognitoBrowserContext(); + context = await browser.createBrowserContext(); cleanupStorage.push(() => { return context.close(); }); diff --git a/remote/test/puppeteer/test/src/navigation.spec.ts b/remote/test/puppeteer/test/src/navigation.spec.ts index 1f3a51f58a..dd59c98349 100644 --- a/remote/test/puppeteer/test/src/navigation.spec.ts +++ b/remote/test/puppeteer/test/src/navigation.spec.ts @@ -154,10 +154,10 @@ describe('navigation', function () { }); const EXPECTED_SSL_CERT_MESSAGE_REGEX = - /net::ERR_CERT_INVALID|net::ERR_CERT_AUTHORITY_INVALID/; + /net::ERR_CERT_INVALID|net::ERR_CERT_AUTHORITY_INVALID|MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT|SSL_ERROR_UNKNOWN/; it('should fail when navigating to bad SSL', async () => { - const {page, httpsServer, isChrome} = await getTestState(); + const {page, httpsServer} = await getTestState(); // Make sure that network events do not emit 'undefined'. // @see https://crbug.com/750469 @@ -176,18 +176,14 @@ describe('navigation', function () { await page.goto(httpsServer.EMPTY_PAGE).catch(error_ => { return (error = error_); }); - if (isChrome) { - expect(error.message).toMatch(EXPECTED_SSL_CERT_MESSAGE_REGEX); - } else { - expect(error.message).toContain('SSL_ERROR_UNKNOWN'); - } + expect(error.message).toMatch(EXPECTED_SSL_CERT_MESSAGE_REGEX); expect(requests).toHaveLength(2); expect(requests[0]).toBe('request'); expect(requests[1]).toBe('requestfailed'); }); it('should fail when navigating to bad SSL after redirects', async () => { - const {page, server, httpsServer, isChrome} = await getTestState(); + const {page, server, httpsServer} = await getTestState(); server.setRedirect('/redirect/1.html', '/redirect/2.html'); server.setRedirect('/redirect/2.html', '/empty.html'); @@ -195,17 +191,10 @@ describe('navigation', function () { await page.goto(httpsServer.PREFIX + '/redirect/1.html').catch(error_ => { return (error = error_); }); - if (isChrome) { - expect(error.message).toMatch(EXPECTED_SSL_CERT_MESSAGE_REGEX); - } else { - expect(error.message).atLeastOneToContain([ - 'MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT', // Firefox WebDriver BiDi. - 'SSL_ERROR_UNKNOWN ', // Others. - ]); - } + expect(error.message).toMatch(EXPECTED_SSL_CERT_MESSAGE_REGEX); }); it('should fail when main resources failed to load', async () => { - const {page, isChrome} = await getTestState(); + const {page} = await getTestState(); let error!: Error; await page @@ -213,11 +202,9 @@ describe('navigation', function () { .catch(error_ => { return (error = error_); }); - if (isChrome) { - expect(error.message).toContain('net::ERR_CONNECTION_REFUSED'); - } else { - expect(error.message).toContain('NS_ERROR_CONNECTION_REFUSED'); - } + expect(error.message).toMatch( + /net::ERR_CONNECTION_REFUSED|NS_ERROR_CONNECTION_REFUSED/ + ); }); it('should fail when exceeding maximum navigation timeout', async () => { const {page, server} = await getTestState(); diff --git a/remote/test/puppeteer/test/src/oopif.spec.ts b/remote/test/puppeteer/test/src/oopif.spec.ts index c024b76aba..0213e14d5d 100644 --- a/remote/test/puppeteer/test/src/oopif.spec.ts +++ b/remote/test/puppeteer/test/src/oopif.spec.ts @@ -5,10 +5,9 @@ */ import expect from 'expect'; -import type {BrowserContext} from 'puppeteer-core/internal/api/BrowserContext.js'; import type {CDPSession} from 'puppeteer-core/internal/api/CDPSession.js'; import {CDPSessionEvent} from 'puppeteer-core/internal/api/CDPSession.js'; -import type {CdpTarget} from 'puppeteer-core/internal/cdp/Target.js'; +import type {Page} from 'puppeteer-core/internal/api/Page.js'; import {getTestState, launch} from './mocha-utils.js'; import {attachFrame, detachFrame, navigateFrame} from './utils.js'; @@ -33,7 +32,7 @@ describe('OOPIF', function () { }); beforeEach(async () => { - state.context = await state.browser.createIncognitoBrowserContext(); + state.context = await state.browser.createBrowserContext(); state.page = await state.context.newPage(); }); @@ -222,7 +221,7 @@ describe('OOPIF', function () { it('should provide access to elements', async () => { const {server, isHeadless, headless, page} = state; - if (!isHeadless || headless === 'new') { + if (!isHeadless || headless === 'true') { // TODO: this test is partially blocked on crbug.com/1334119. Enable test once // the upstream is fixed. // TLDR: when we dispatch events to the frame the compositor might @@ -266,24 +265,24 @@ describe('OOPIF', function () { await frame.waitForSelector('#clicked'); }); it('should report oopif frames', async () => { - const {server, page, context} = state; + const {server, page} = state; const frame = page.waitForFrame(frame => { return frame.url().endsWith('/oopif.html'); }); await page.goto(server.PREFIX + '/dynamic-oopif.html'); await frame; - expect(oopifs(context)).toHaveLength(1); + expect(await iframes(page)).toHaveLength(1); expect(page.frames()).toHaveLength(2); }); it('should wait for inner OOPIFs', async () => { - const {server, page, context} = state; + const {server, page} = state; await page.goto(`http://mainframe:${server.PORT}/main-frame.html`); const frame2 = await page.waitForFrame(frame => { return frame.url().endsWith('inner-frame2.html'); }); - expect(oopifs(context)).toHaveLength(2); + expect(await iframes(page)).toHaveLength(2); expect( page.frames().filter(frame => { return frame.isOOPFrame(); @@ -297,7 +296,7 @@ describe('OOPIF', function () { }); it('should load oopif iframes with subresources and request interception', async () => { - const {server, page, context} = state; + const {server, page} = state; const framePromise = page.waitForFrame(frame => { return frame.url().endsWith('/oopif.html'); @@ -312,7 +311,7 @@ describe('OOPIF', function () { await page.goto(server.PREFIX + '/dynamic-oopif.html'); const frame = await framePromise; const request = await requestPromise; - expect(oopifs(context)).toHaveLength(1); + expect(await iframes(page)).toHaveLength(1); expect(request.frame()).toBe(frame); }); @@ -394,14 +393,14 @@ describe('OOPIF', function () { }); it('should detect existing OOPIFs when Puppeteer connects to an existing page', async () => { - const {server, puppeteer, page, context} = state; + const {server, puppeteer, page} = state; const frame = page.waitForFrame(frame => { return frame.url().endsWith('/oopif.html'); }); await page.goto(server.PREFIX + '/dynamic-oopif.html'); await frame; - expect(oopifs(context)).toHaveLength(1); + expect(await iframes(page)).toHaveLength(1); expect(page.frames()).toHaveLength(2); const browserURL = 'http://127.0.0.1:21222'; @@ -472,7 +471,7 @@ describe('OOPIF', function () { const {server, page} = state; // Setup our session listeners to observe OOPIF activity. - const session = await page.target().createCDPSession(); + const session = await page.createCDPSession(); const networkEvents: string[] = []; const otherSessions: CDPSession[] = []; await session.send('Target.setAutoAttach', { @@ -520,8 +519,13 @@ describe('OOPIF', function () { }); }); -function oopifs(context: BrowserContext) { - return context.targets().filter(target => { - return (target as CdpTarget)._getTargetInfo().type === 'iframe'; +async function iframes(page: Page) { + const iframes = await Promise.all( + page.frames().map(async frame => { + return await frame.frameElement(); + }) + ); + return iframes.filter(frame => { + return frame !== null; }); } diff --git a/remote/test/puppeteer/test/src/page.spec.ts b/remote/test/puppeteer/test/src/page.spec.ts index 79fc69ebbc..d83920d3ff 100644 --- a/remote/test/puppeteer/test/src/page.spec.ts +++ b/remote/test/puppeteer/test/src/page.spec.ts @@ -15,6 +15,7 @@ import type {HTTPRequest} from 'puppeteer-core/internal/api/HTTPRequest.js'; import type {Metrics, Page} from 'puppeteer-core/internal/api/Page.js'; import type {CdpPage} from 'puppeteer-core/internal/cdp/Page.js'; import type {ConsoleMessage} from 'puppeteer-core/internal/common/ConsoleMessage.js'; +import {Deferred} from 'puppeteer-core/internal/util/Deferred.js'; import sinon from 'sinon'; import {getTestState, setupTestBrowserHooks} from './mocha-utils.js'; @@ -42,9 +43,9 @@ describe('Page', function () { expect(error.message).toContain('Protocol error'); }); it('should not be visible in browser.pages', async () => { - const {browser} = await getTestState(); + const {browser, context} = await getTestState(); - const newPage = await browser.newPage(); + const newPage = await context.newPage(); expect(await browser.pages()).toContain(newPage); await newPage.close(); expect(await browser.pages()).not.toContain(newPage); @@ -102,7 +103,11 @@ describe('Page', function () { ]); for (let i = 0; i < 2; i++) { const message = results[i].message; - expect(message).atLeastOneToContain(['Target closed', 'Page closed!']); + expect(message).atLeastOneToContain([ + 'Target closed', + 'Page closed!', + 'Frame detached', + ]); expect(message).not.toContain('Timeout'); } }); @@ -445,7 +450,7 @@ describe('Page', function () { messages.map(msg => { return msg.type(); }) - ).toEqual(['trace', 'dir', 'warning', 'error', 'log']); + ).toEqual(['trace', 'dir', 'warn', 'error', 'log']); expect( messages.map(msg => { return msg.text(); @@ -492,6 +497,21 @@ describe('Page', function () { 'JSHandle@window', ]); }); + it('should return remote objects', async () => { + const {page} = await getTestState(); + + const logPromise = waitEvent<ConsoleMessage>(page, 'console'); + await page.evaluate(() => { + (globalThis as any).test = 1; + console.log(1, 2, 3, globalThis); + }); + const log = await logPromise; + expect(log.text()).toBe('1 2 3 JSHandle@object'); + expect(log.args()).toHaveLength(4); + expect(await (await log.args()[3]!.getProperty('test')).jsonValue()).toBe( + 1 + ); + }); it('should trigger correct Log', async () => { const {page, server, isChrome} = await getTestState(); @@ -583,14 +603,10 @@ describe('Page', function () { // 3. After that, remove the iframe. frame.remove(); }); - const popupTarget = page - .browserContext() - .targets() - .find(target => { - return target !== page.target(); - })!; - // 4. Connect to the popup and make sure it doesn't throw. - await popupTarget.page(); + // 4. The target will always be the last one. + const popupTarget = page.browserContext().targets().at(-1)!; + // 5. Connect to the popup and make sure it doesn't throw and is not the same page. + expect(await popupTarget.page()).not.toBe(page); }); }); @@ -1015,15 +1031,15 @@ describe('Page', function () { it('should be callable from-inside evaluateOnNewDocument', async () => { const {page} = await getTestState(); - let called = false; + const called = new Deferred<void>(); await page.exposeFunction('woof', function () { - called = true; + called.resolve(); }); await page.evaluateOnNewDocument(() => { return (globalThis as any).woof(); }); await page.reload(); - expect(called).toBe(true); + await called.valueOrThrow(); }); it('should survive navigation', async () => { const {page, server} = await getTestState(); @@ -1217,7 +1233,7 @@ describe('Page', function () { page.goto(server.PREFIX + '/error.html'), ]); expect(error.message).toContain('Fancy'); - expect(error.stack?.split('\n')[1]).toContain('error.html:13'); + expect(error.stack?.split('\n').at(-1)).toContain('error.html:3:1'); }); }); @@ -1940,33 +1956,20 @@ describe('Page', function () { } }); - it('can print to PDF with accessible', async () => { - const {page, server} = await getTestState(); - - const outputFile = __dirname + '/../assets/output.pdf'; - const outputFileAccessible = - __dirname + '/../assets/output-accessible.pdf'; - await page.goto(server.PREFIX + '/pdf.html'); - await page.pdf({path: outputFile}); - await page.pdf({path: outputFileAccessible, tagged: true}); - try { - expect( - fs.readFileSync(outputFileAccessible).byteLength - ).toBeGreaterThan(fs.readFileSync(outputFile).byteLength); - } finally { - fs.unlinkSync(outputFileAccessible); - fs.unlinkSync(outputFile); - } - }); - it('can print to PDF and stream the result', async () => { const {page} = await getTestState(); const stream = await page.createPDFStream(); let size = 0; - for await (const chunk of stream) { - size += chunk.length; + const reader = stream.getReader(); + while (true) { + const {done, value} = await reader.read(); + if (done) { + break; + } + size += value.length; } + expect(size).toBeGreaterThan(0); }); @@ -2252,9 +2255,9 @@ describe('Page', function () { describe('Page.bringToFront', function () { it('should work', async () => { - const {browser} = await getTestState(); - const page1 = await browser.newPage(); - const page2 = await browser.newPage(); + const {context} = await getTestState(); + const page1 = await context.newPage(); + const page2 = await context.newPage(); await page1.bringToFront(); expect( diff --git a/remote/test/puppeteer/test/src/proxy.spec.ts b/remote/test/puppeteer/test/src/proxy.spec.ts index 07b73cdd0d..1b79cd6665 100644 --- a/remote/test/puppeteer/test/src/proxy.spec.ts +++ b/remote/test/puppeteer/test/src/proxy.spec.ts @@ -150,7 +150,7 @@ describe('request proxy', () => { args: [...defaultArgs, `--proxy-server=${proxyServerUrl}`], }); try { - const context = await browser.createIncognitoBrowserContext(); + const context = await browser.createBrowserContext(); const page = await context.newPage(); const response = (await page.goto(emptyPageUrl))!; @@ -174,7 +174,7 @@ describe('request proxy', () => { ], }); try { - const context = await browser.createIncognitoBrowserContext(); + const context = await browser.createBrowserContext(); const page = await context.newPage(); const response = (await page.goto(emptyPageUrl))!; @@ -197,7 +197,7 @@ describe('request proxy', () => { args: defaultArgs, }); try { - const context = await browser.createIncognitoBrowserContext({ + const context = await browser.createBrowserContext({ proxyServer: proxyServerUrl, }); const page = await context.newPage(); @@ -219,7 +219,7 @@ describe('request proxy', () => { args: defaultArgs, }); try { - const context = await browser.createIncognitoBrowserContext({ + const context = await browser.createBrowserContext({ proxyServer: proxyServerUrl, proxyBypassList: [new URL(emptyPageUrl).host], }); diff --git a/remote/test/puppeteer/test/src/queryselector.spec.ts b/remote/test/puppeteer/test/src/queryselector.spec.ts index 7fd27f914f..c8df118e5f 100644 --- a/remote/test/puppeteer/test/src/queryselector.spec.ts +++ b/remote/test/puppeteer/test/src/queryselector.spec.ts @@ -174,29 +174,29 @@ describe('querySelector', function () { const elements = await page.$$('div'); expect(elements).toHaveLength(0); }); - }); - describe('Page.$x', function () { - it('should query existing element', async () => { - const {page} = await getTestState(); + describe('xpath', function () { + it('should query existing element', async () => { + const {page} = await getTestState(); - await page.setContent('<section>test</section>'); - const elements = await page.$x('/html/body/section'); - expect(elements[0]).toBeTruthy(); - expect(elements).toHaveLength(1); - }); - it('should return empty array for non-existing element', async () => { - const {page} = await getTestState(); + await page.setContent('<section>test</section>'); + const elements = await page.$$('xpath/html/body/section'); + expect(elements[0]).toBeTruthy(); + expect(elements).toHaveLength(1); + }); + it('should return empty array for non-existing element', async () => { + const {page} = await getTestState(); - const element = await page.$x('/html/body/non-existing-element'); - expect(element).toEqual([]); - }); - it('should return multiple elements', async () => { - const {page} = await getTestState(); + const element = await page.$$('xpath/html/body/non-existing-element'); + expect(element).toEqual([]); + }); + it('should return multiple elements', async () => { + const {page} = await getTestState(); - await page.setContent('<div></div><div></div>'); - const elements = await page.$x('/html/body/div'); - expect(elements).toHaveLength(2); + await page.setContent('<div></div><div></div>'); + const elements = await page.$$('xpath/html/body/div'); + expect(elements).toHaveLength(2); + }); }); }); @@ -347,37 +347,40 @@ describe('querySelector', function () { const elements = await html.$$('div'); expect(elements).toHaveLength(0); }); - }); - describe('ElementHandle.$x', function () { - it('should query existing element', async () => { - const {page, server} = await getTestState(); - - await page.goto(server.PREFIX + '/playground.html'); - await page.setContent( - '<html><body><div class="second"><div class="inner">A</div></div></body></html>' - ); - using html = (await page.$('html'))!; - const second = await html.$x(`./body/div[contains(@class, 'second')]`); - const inner = await second[0]!.$x(`./div[contains(@class, 'inner')]`); - const content = await page.evaluate(e => { - return e.textContent; - }, inner[0]!); - expect(content).toBe('A'); - }); + describe('xpath', function () { + it('should query existing element', async () => { + const {page, server} = await getTestState(); + + await page.goto(server.PREFIX + '/playground.html'); + await page.setContent( + '<html><body><div class="second"><div class="inner">A</div></div></body></html>' + ); + using html = (await page.$('html'))!; + const second = await html.$$( + `xpath/./body/div[contains(@class, 'second')]` + ); + const inner = await second[0]!.$$( + `xpath/./div[contains(@class, 'inner')]` + ); + const content = await page.evaluate(e => { + return e.textContent; + }, inner[0]!); + expect(content).toBe('A'); + }); - it('should return null for non-existing element', async () => { - const {page} = await getTestState(); + it('should return null for non-existing element', async () => { + const {page} = await getTestState(); - await page.setContent( - '<html><body><div class="second"><div class="inner">B</div></div></body></html>' - ); - using html = (await page.$('html'))!; - const second = await html.$x(`/div[contains(@class, 'third')]`); - expect(second).toEqual([]); + await page.setContent( + '<html><body><div class="second"><div class="inner">B</div></div></body></html>' + ); + using html = (await page.$('html'))!; + const second = await html.$$(`xpath/div[contains(@class, 'third')]`); + expect(second).toEqual([]); + }); }); }); - // This is the same tests for `$$eval` and `$$` as above, but with a queryAll // handler that returns an array instead of a list of nodes. describe('QueryAll', function () { diff --git a/remote/test/puppeteer/test/src/requestinterception.spec.ts b/remote/test/puppeteer/test/src/requestinterception.spec.ts index 45827bb3cf..4b88d30a3b 100644 --- a/remote/test/puppeteer/test/src/requestinterception.spec.ts +++ b/remote/test/puppeteer/test/src/requestinterception.spec.ts @@ -118,7 +118,7 @@ describe('request interception', function () { await page.goto(server.EMPTY_PAGE); await page.setRequestInterception(true); - const cdp = await page.target().createCDPSession(); + const cdp = await page.createCDPSession(); await cdp.send('DOM.enable'); const urls: string[] = []; page.on('request', request => { diff --git a/remote/test/puppeteer/test/src/screenshot.spec.ts b/remote/test/puppeteer/test/src/screenshot.spec.ts index ad53b60e95..9176d0c920 100644 --- a/remote/test/puppeteer/test/src/screenshot.spec.ts +++ b/remote/test/puppeteer/test/src/screenshot.spec.ts @@ -8,12 +8,7 @@ import assert from 'assert'; import expect from 'expect'; -import { - getTestState, - isHeadless, - launch, - setupTestBrowserHooks, -} from './mocha-utils.js'; +import {getTestState, launch, setupTestBrowserHooks} from './mocha-utils.js'; describe('Screenshots', function () { setupTestBrowserHooks(); @@ -366,7 +361,7 @@ describe('Screenshots', function () { it('should run in parallel in multiple pages', async () => { const {browser, server} = await getTestState(); - const context = await browser.createIncognitoBrowserContext(); + const context = await browser.createBrowserContext(); const N = 2; const pages = await Promise.all( @@ -436,18 +431,15 @@ describe('Screenshots', function () { }); expect(screenshot).toBeGolden('white.jpg'); }); - (!isHeadless ? it : it.skip)( - 'should work in "fromSurface: false" mode', - async () => { - const {page, server} = await getTestState(); + it('should work in "fromSurface: false" mode', async () => { + const {page, server} = await getTestState(); - await page.setViewport({width: 500, height: 500}); - await page.goto(server.PREFIX + '/grid.html'); - const screenshot = await page.screenshot({ - fromSurface: false, - }); - expect(screenshot).toBeDefined(); // toBeGolden('screenshot-fromsurface-false.png'); - } - ); + await page.setViewport({width: 500, height: 500}); + await page.goto(server.PREFIX + '/grid.html'); + const screenshot = await page.screenshot({ + fromSurface: false, + }); + expect(screenshot).toBeDefined(); + }); }); }); diff --git a/remote/test/puppeteer/test/src/target.spec.ts b/remote/test/puppeteer/test/src/target.spec.ts index 28d17a4030..6c1b9cb95e 100644 --- a/remote/test/puppeteer/test/src/target.spec.ts +++ b/remote/test/puppeteer/test/src/target.spec.ts @@ -179,6 +179,29 @@ describe('Target', function () { }) ).toBe('[object ServiceWorkerGlobalScope]'); }); + + it('should close a service worker', async () => { + const {page, server, context} = await getTestState(); + + await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html'); + + const target = await context.waitForTarget( + target => { + return target.type() === 'service_worker'; + }, + {timeout: 3000} + ); + const worker = (await target.worker())!; + + const onceDestroyed = new Promise(resolve => { + context.once('targetdestroyed', event => { + resolve(event); + }); + }); + await worker.close(); + expect(await onceDestroyed).toBe(target); + }); + it('should create a worker from a shared worker', async () => { const {page, server, context} = await getTestState(); @@ -199,6 +222,31 @@ describe('Target', function () { }) ).toBe('[object SharedWorkerGlobalScope]'); }); + + it('should close a shared worker', async () => { + const {page, server, context} = await getTestState(); + + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => { + new SharedWorker('data:text/javascript,console.log("hi2")'); + }); + const target = await context.waitForTarget( + target => { + return target.type() === 'shared_worker'; + }, + {timeout: 3000} + ); + const worker = (await target.worker())!; + + const onceDestroyed = new Promise(resolve => { + context.once('targetdestroyed', event => { + resolve(event); + }); + }); + await worker.close(); + expect(await onceDestroyed).toBe(target); + }); + it('should report when a target url changes', async () => { const {page, server, context} = await getTestState(); @@ -285,7 +333,7 @@ describe('Target', function () { describe('Browser.waitForTarget', () => { it('should wait for a target', async () => { - const {browser, server} = await getTestState(); + const {browser, server, context} = await getTestState(); let resolved = false; const targetPromise = browser.waitForTarget( @@ -306,7 +354,7 @@ describe('Target', function () { throw error; } }); - const page = await browser.newPage(); + const page = await context.newPage(); expect(resolved).toBe(false); await page.goto(server.EMPTY_PAGE); try { diff --git a/remote/test/puppeteer/test/src/touchscreen.spec.ts b/remote/test/puppeteer/test/src/touchscreen.spec.ts index 28a18ec449..94d8e6fecb 100644 --- a/remote/test/puppeteer/test/src/touchscreen.spec.ts +++ b/remote/test/puppeteer/test/src/touchscreen.spec.ts @@ -15,65 +15,235 @@ describe('Touchscreen', () => { describe('Touchscreen.prototype.tap', () => { it('should work', async () => { - const {page, server, isHeadless} = await getTestState(); + const {page, server} = await getTestState(); await page.goto(server.PREFIX + '/input/touchscreen.html'); await page.tap('button'); expect( - ( - await page.evaluate(() => { - return allEvents; - }) - ).filter(({type}) => { - return type !== 'pointermove' || isHeadless; + await page.evaluate(() => { + return allEvents; }) ).toMatchObject([ - {height: 1, type: 'pointerdown', width: 1, x: 5, y: 5}, - {touches: [[5, 5, 0.5, 0.5]], type: 'touchstart'}, - {height: 1, type: 'pointerup', width: 1, x: 5, y: 5}, - {touches: [[5, 5, 0.5, 0.5]], type: 'touchend'}, - {height: 1, type: 'click', width: 1, x: 5, y: 5}, + { + type: 'pointerdown', + x: 5, + y: 5, + width: 1, + height: 1, + altitudeAngle: Math.PI / 2, + azimuthAngle: 0, + pressure: 0.5, + pointerType: 'touch', + twist: 0, + tiltX: 0, + tiltY: 0, + }, + { + type: 'touchstart', + changedTouches: [ + {clientX: 5, clientY: 5, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + activeTouches: [ + {clientX: 5, clientY: 5, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + }, + { + type: 'pointerup', + x: 5, + y: 5, + width: 1, + height: 1, + altitudeAngle: Math.PI / 2, + azimuthAngle: 0, + pressure: 0, + pointerType: 'touch', + twist: 0, + tiltX: 0, + tiltY: 0, + }, + { + type: 'touchend', + changedTouches: [ + {clientX: 5, clientY: 5, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + activeTouches: [], + }, + { + type: 'click', + x: 5, + y: 5, + width: 1, + height: 1, + altitudeAngle: Math.PI / 2, + azimuthAngle: 0, + pressure: 0, + pointerType: 'touch', + twist: 0, + tiltX: 0, + tiltY: 0, + }, ]); }); }); describe('Touchscreen.prototype.touchMove', () => { it('should work', async () => { - const {page, server, isHeadless} = await getTestState(); + const {page, server} = await getTestState(); await page.goto(server.PREFIX + '/input/touchscreen.html'); + // Note that touchmoves are sometimes not triggered if consecutive + // touchmoves are less than 15 pixels. + // + // See https://github.com/puppeteer/puppeteer/issues/10836 await page.touchscreen.touchStart(0, 0); - await page.touchscreen.touchMove(10, 10); - await page.touchscreen.touchMove(15.5, 15); - await page.touchscreen.touchMove(20, 20.4); - await page.touchscreen.touchMove(40, 30); + await page.touchscreen.touchMove(15, 15); + await page.touchscreen.touchMove(30.5, 30); + await page.touchscreen.touchMove(50, 45.4); + await page.touchscreen.touchMove(80, 50); await page.touchscreen.touchEnd(); + expect( - ( - await page.evaluate(() => { - return allEvents; - }) - ).filter(({type}) => { - return type !== 'pointermove' || isHeadless; - }) - ).toMatchObject( - [ - {type: 'pointerdown', x: 0, y: 0, width: 1, height: 1}, - {type: 'touchstart', touches: [[0, 0, 0.5, 0.5]]}, - {type: 'pointermove', x: 10, y: 10, width: 1, height: 1}, - {type: 'touchmove', touches: [[10, 10, 0.5, 0.5]]}, - {type: 'pointermove', x: 16, y: 15, width: 1, height: 1}, - {type: 'touchmove', touches: [[16, 15, 0.5, 0.5]]}, - {type: 'pointermove', x: 20, y: 20, width: 1, height: 1}, - {type: 'touchmove', touches: [[20, 20, 0.5, 0.5]]}, - {type: 'pointermove', x: 40, y: 30, width: 1, height: 1}, - {type: 'touchmove', touches: [[40, 30, 0.5, 0.5]]}, - {type: 'pointerup', x: 40, y: 30, width: 1, height: 1}, - {type: 'touchend', touches: [[40, 30, 0.5, 0.5]]}, - ].filter(({type}) => { - return type !== 'pointermove' || isHeadless; + await page.evaluate(() => { + return allEvents; }) - ); + ).toMatchObject([ + { + type: 'pointerdown', + x: 0, + y: 0, + width: 1, + height: 1, + altitudeAngle: 1.5707963267948966, + azimuthAngle: 0, + pressure: 0.5, + pointerType: 'touch', + twist: 0, + tiltX: 0, + tiltY: 0, + }, + { + type: 'touchstart', + changedTouches: [ + {clientX: 0, clientY: 0, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + activeTouches: [ + {clientX: 0, clientY: 0, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + }, + { + type: 'pointermove', + x: 15, + y: 15, + width: 1, + height: 1, + altitudeAngle: 1.5707963267948966, + azimuthAngle: 0, + pressure: 0.5, + pointerType: 'touch', + twist: 0, + tiltX: 0, + tiltY: 0, + }, + { + type: 'touchmove', + changedTouches: [ + {clientX: 15, clientY: 15, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + activeTouches: [ + {clientX: 15, clientY: 15, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + }, + { + type: 'pointermove', + x: 31, + y: 30, + width: 1, + height: 1, + altitudeAngle: 1.5707963267948966, + azimuthAngle: 0, + pressure: 0.5, + pointerType: 'touch', + twist: 0, + tiltX: 0, + tiltY: 0, + }, + { + type: 'touchmove', + changedTouches: [ + {clientX: 31, clientY: 30, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + activeTouches: [ + {clientX: 31, clientY: 30, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + }, + { + type: 'pointermove', + x: 50, + y: 45, + width: 1, + height: 1, + altitudeAngle: 1.5707963267948966, + azimuthAngle: 0, + pressure: 0.5, + pointerType: 'touch', + twist: 0, + tiltX: 0, + tiltY: 0, + }, + { + type: 'touchmove', + changedTouches: [ + {clientX: 50, clientY: 45, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + activeTouches: [ + {clientX: 50, clientY: 45, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + }, + { + type: 'pointermove', + x: 80, + y: 50, + width: 1, + height: 1, + altitudeAngle: 1.5707963267948966, + azimuthAngle: 0, + pressure: 0.5, + pointerType: 'touch', + twist: 0, + tiltX: 0, + tiltY: 0, + }, + { + type: 'touchmove', + changedTouches: [ + {clientX: 80, clientY: 50, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + activeTouches: [ + {clientX: 80, clientY: 50, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + }, + { + type: 'pointerup', + x: 80, + y: 50, + width: 1, + height: 1, + altitudeAngle: 1.5707963267948966, + azimuthAngle: 0, + pressure: 0, + pointerType: 'touch', + twist: 0, + tiltX: 0, + tiltY: 0, + }, + { + type: 'touchend', + changedTouches: [ + {clientX: 80, clientY: 50, radiusX: 0.5, radiusY: 0.5, force: 0.5}, + ], + activeTouches: [], + }, + ]); }); }); }); diff --git a/remote/test/puppeteer/test/src/tracing.spec.ts b/remote/test/puppeteer/test/src/tracing.spec.ts index 2c0a5aff19..7ee6d46192 100644 --- a/remote/test/puppeteer/test/src/tracing.spec.ts +++ b/remote/test/puppeteer/test/src/tracing.spec.ts @@ -8,6 +8,8 @@ import fs from 'fs'; import path from 'path'; import expect from 'expect'; +import * as utils from 'puppeteer-core/internal/common/util.js'; +import sinon from 'sinon'; import {launch} from './mocha-utils.js'; @@ -113,16 +115,26 @@ describe('Tracing', function () { await page.tracing.start({screenshots: true}); await page.goto(server.PREFIX + '/grid.html'); - const oldBufferConcat = Buffer.concat; - try { - Buffer.concat = () => { - throw new Error('error'); - }; - const trace = await page.tracing.stop(); - expect(trace).toEqual(undefined); - } finally { - Buffer.concat = oldBufferConcat; - } + const oldGetReadableAsBuffer = utils.getReadableAsBuffer; + sinon.stub(utils, 'getReadableAsBuffer').callsFake(() => { + return oldGetReadableAsBuffer({ + getReader() { + return { + done: false, + read() { + if (!this.done) { + this.done = true; + return {done: false, value: 42}; + } + return {done: true}; + }, + }; + }, + } as unknown as ReadableStream); + }); + + const trace = await page.tracing.stop(); + expect(trace).toEqual(undefined); }); it('should support a buffer without a path', async () => { diff --git a/remote/test/puppeteer/test/src/waittask.spec.ts b/remote/test/puppeteer/test/src/waittask.spec.ts index 8ff52db16f..b9a28c9e7a 100644 --- a/remote/test/puppeteer/test/src/waittask.spec.ts +++ b/remote/test/puppeteer/test/src/waittask.spec.ts @@ -336,39 +336,6 @@ describe('waittask specs', function () { }); }); - describe('Page.waitForTimeout', () => { - it('waits for the given timeout before resolving', async () => { - const {page, server} = await getTestState(); - await page.goto(server.EMPTY_PAGE); - const startTime = Date.now(); - await page.waitForTimeout(1000); - const endTime = Date.now(); - /* In a perfect world endTime - startTime would be exactly 1000 but we - * expect some fluctuations and for it to be off by a little bit. So to - * avoid a flaky test we'll make sure it waited for roughly 1 second. - */ - expect(endTime - startTime).toBeGreaterThan(700); - expect(endTime - startTime).toBeLessThan(1300); - }); - }); - - describe('Frame.waitForTimeout', () => { - it('waits for the given timeout before resolving', async () => { - const {page, server} = await getTestState(); - await page.goto(server.EMPTY_PAGE); - const frame = page.mainFrame(); - const startTime = Date.now(); - await frame.waitForTimeout(1000); - const endTime = Date.now(); - /* In a perfect world endTime - startTime would be exactly 1000 but we - * expect some fluctuations and for it to be off by a little bit. So to - * avoid a flaky test we'll make sure it waited for roughly 1 second - */ - expect(endTime - startTime).toBeGreaterThan(700); - expect(endTime - startTime).toBeLessThan(1300); - }); - }); - describe('Frame.waitForSelector', function () { const addElement = (tag: string) => { return document.body.appendChild(document.createElement(tag)); @@ -479,9 +446,10 @@ describe('waittask specs', function () { await detachFrame(page, 'frame1'); await waitPromise; expect(waitError).toBeTruthy(); - expect(waitError?.message).toContain( - 'waitForFunction failed: frame got detached.' - ); + expect(waitError?.message).atLeastOneToContain([ + 'waitForFunction failed: frame got detached.', + 'Browsing context already closed.', + ]); }); it('should survive cross-process navigation', async () => { const {page, server} = await getTestState(); @@ -726,142 +694,151 @@ describe('waittask specs', function () { // The extension is ts here as Mocha maps back via sourcemaps. expect(error?.stack).toContain('WaitTask.ts'); }); - }); - describe('Frame.waitForXPath', function () { - const addElement = (tag: string) => { - return document.body.appendChild(document.createElement(tag)); - }; - - it('should support some fancy xpath', async () => { - const {page} = await getTestState(); + describe('xpath', function () { + const addElement = (tag: string) => { + return document.body.appendChild(document.createElement(tag)); + }; - await page.setContent(`<p>red herring</p><p>hello world </p>`); - const waitForXPath = page.waitForXPath( - '//p[normalize-space(.)="hello world"]' - ); - expect( - await page.evaluate( - x => { - return x?.textContent; - }, - await waitForXPath - ) - ).toBe('hello world '); - }); - it('should respect timeout', async () => { - const {page} = await getTestState(); + it('should support some fancy xpath', async () => { + const {page} = await getTestState(); - let error!: Error; - await page.waitForXPath('//div', {timeout: 10}).catch(error_ => { - return (error = error_); + await page.setContent(`<p>red herring</p><p>hello world </p>`); + const waitForSelector = page.waitForSelector( + 'xpath/.//p[normalize-space(.)="hello world"]' + ); + expect( + await page.evaluate( + x => { + return x?.textContent; + }, + await waitForSelector + ) + ).toBe('hello world '); }); - expect(error).toBeInstanceOf(TimeoutError); - expect(error?.message).toContain('Waiting failed: 10ms exceeded'); - }); - it('should run in specified frame', async () => { - const {page, server} = await getTestState(); + it('should respect timeout', async () => { + const {page} = await getTestState(); - await attachFrame(page, 'frame1', server.EMPTY_PAGE); - await attachFrame(page, 'frame2', server.EMPTY_PAGE); - const frame1 = page.frames()[1]!; - const frame2 = page.frames()[2]!; - const waitForXPathPromise = frame2.waitForXPath('//div'); - await frame1.evaluate(addElement, 'div'); - await frame2.evaluate(addElement, 'div'); - using eHandle = await waitForXPathPromise; - expect(eHandle?.frame).toBe(frame2); - }); - it('should throw when frame is detached', async () => { - const {page, server} = await getTestState(); - - await attachFrame(page, 'frame1', server.EMPTY_PAGE); - const frame = page.frames()[1]!; - let waitError: Error | undefined; - const waitPromise = frame - .waitForXPath('//*[@class="box"]') - .catch(error => { - return (waitError = error); - }); - await detachFrame(page, 'frame1'); - await waitPromise; - expect(waitError).toBeTruthy(); - expect(waitError?.message).toContain( - 'waitForFunction failed: frame got detached.' - ); - }); - it('hidden should wait for display: none', async () => { - const {page} = await getTestState(); - - let divHidden = false; - await page.setContent(`<div style='display: block;'>text</div>`); - const waitForXPath = page - .waitForXPath('//div', {hidden: true}) - .then(() => { - return (divHidden = true); + let error!: Error; + await page + .waitForSelector('xpath/.//div', {timeout: 10}) + .catch(error_ => { + return (error = error_); + }); + expect(error).toBeInstanceOf(TimeoutError); + expect(error?.message).toContain('Waiting failed: 10ms exceeded'); + }); + it('should run in specified frame', async () => { + const {page, server} = await getTestState(); + + await attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame2', server.EMPTY_PAGE); + const frame1 = page.frames()[1]!; + const frame2 = page.frames()[2]!; + const waitForSelector = frame2.waitForSelector('xpath/.//div'); + await frame1.evaluate(addElement, 'div'); + await frame2.evaluate(addElement, 'div'); + using eHandle = await waitForSelector; + expect(eHandle?.frame).toBe(frame2); + }); + it('should throw when frame is detached', async () => { + const {page, server} = await getTestState(); + + await attachFrame(page, 'frame1', server.EMPTY_PAGE); + const frame = page.frames()[1]!; + let waitError: Error | undefined; + const waitPromise = frame + .waitForSelector('xpath/.//*[@class="box"]') + .catch(error => { + return (waitError = error); + }); + await detachFrame(page, 'frame1'); + await waitPromise; + expect(waitError).toBeTruthy(); + expect(waitError?.message).atLeastOneToContain([ + 'waitForFunction failed: frame got detached.', + 'Browsing context already closed.', + ]); + }); + it('hidden should wait for display: none', async () => { + const {page} = await getTestState(); + + let divHidden = false; + await page.setContent(`<div style='display: block;'>text</div>`); + const waitForSelector = page + .waitForSelector('xpath/.//div', {hidden: true}) + .then(() => { + return (divHidden = true); + }); + await page.waitForSelector('xpath/.//div'); // do a round trip + expect(divHidden).toBe(false); + await page.evaluate(() => { + return document + .querySelector('div') + ?.style.setProperty('display', 'none'); }); - await page.waitForXPath('//div'); // do a round trip - expect(divHidden).toBe(false); - await page.evaluate(() => { - return document - .querySelector('div') - ?.style.setProperty('display', 'none'); + expect(await waitForSelector).toBe(true); + expect(divHidden).toBe(true); }); - expect(await waitForXPath).toBe(true); - expect(divHidden).toBe(true); - }); - it('hidden should return null if the element is not found', async () => { - const {page} = await getTestState(); + it('hidden should return null if the element is not found', async () => { + const {page} = await getTestState(); - using waitForXPath = await page.waitForXPath('//div', {hidden: true}); + using waitForSelector = await page.waitForSelector('xpath/.//div', { + hidden: true, + }); - expect(waitForXPath).toBe(null); - }); - it('hidden should return an empty element handle if the element is found', async () => { - const {page} = await getTestState(); + expect(waitForSelector).toBe(null); + }); + it('hidden should return an empty element handle if the element is found', async () => { + const {page} = await getTestState(); - await page.setContent(`<div style='display: none;'>text</div>`); + await page.setContent(`<div style='display: none;'>text</div>`); - using waitForXPath = await page.waitForXPath('//div', {hidden: true}); + using waitForSelector = await page.waitForSelector('xpath/.//div', { + hidden: true, + }); - expect(waitForXPath).toBeInstanceOf(ElementHandle); - }); - it('should return the element handle', async () => { - const {page} = await getTestState(); + expect(waitForSelector).toBeInstanceOf(ElementHandle); + }); + it('should return the element handle', async () => { + const {page} = await getTestState(); - const waitForXPath = page.waitForXPath('//*[@class="zombo"]'); - await page.setContent(`<div class='zombo'>anything</div>`); - expect( - await page.evaluate( - x => { - return x?.textContent; - }, - await waitForXPath - ) - ).toBe('anything'); - }); - it('should allow you to select a text node', async () => { - const {page} = await getTestState(); + const waitForSelector = page.waitForSelector( + 'xpath/.//*[@class="zombo"]' + ); + await page.setContent(`<div class='zombo'>anything</div>`); + expect( + await page.evaluate( + x => { + return x?.textContent; + }, + await waitForSelector + ) + ).toBe('anything'); + }); + it('should allow you to select a text node', async () => { + const {page} = await getTestState(); - await page.setContent(`<div>some text</div>`); - using text = await page.waitForXPath('//div/text()'); - expect(await (await text!.getProperty('nodeType')!).jsonValue()).toBe( - 3 /* Node.TEXT_NODE */ - ); - }); - it('should allow you to select an element with single slash', async () => { - const {page} = await getTestState(); + await page.setContent(`<div>some text</div>`); + using text = await page.waitForSelector('xpath/.//div/text()'); + expect(await (await text!.getProperty('nodeType')!).jsonValue()).toBe( + 3 /* Node.TEXT_NODE */ + ); + }); + it('should allow you to select an element with single slash', async () => { + const {page} = await getTestState(); - await page.setContent(`<div>some text</div>`); - const waitForXPath = page.waitForXPath('/html/body/div'); - expect( - await page.evaluate( - x => { - return x?.textContent; - }, - await waitForXPath - ) - ).toBe('some text'); + await page.setContent(`<div>some text</div>`); + const waitForSelector = page.waitForSelector('xpath/html/body/div'); + expect( + await page.evaluate( + x => { + return x?.textContent; + }, + await waitForSelector + ) + ).toBe('some text'); + }); }); }); }); diff --git a/remote/test/puppeteer/test/src/worker.spec.ts b/remote/test/puppeteer/test/src/worker.spec.ts index 254ff4a514..b5b7159e6a 100644 --- a/remote/test/puppeteer/test/src/worker.spec.ts +++ b/remote/test/puppeteer/test/src/worker.spec.ts @@ -52,7 +52,10 @@ describe('Workers', function () { const error = await workerThisObj.getProperty('self').catch(error => { return error; }); - expect(error.message).toContain('Most likely the worker has been closed.'); + expect(error.message).atLeastOneToContain([ + 'Most likely the worker has been closed.', + 'Realm already destroyed.', + ]); }); it('should report console logs', async () => { const {page} = await getTestState(); @@ -70,7 +73,7 @@ describe('Workers', function () { columnNumber: 8, }); }); - it('should have JSHandles for console logs', async () => { + it('should work with console logs', async () => { const {page} = await getTestState(); const logPromise = waitEvent<ConsoleMessage>(page, 'console'); @@ -80,9 +83,6 @@ describe('Workers', function () { const log = await logPromise; expect(log.text()).toBe('1 2 3 JSHandle@object'); expect(log.args()).toHaveLength(4); - expect(await (await log.args()[3]!.getProperty('origin')).jsonValue()).toBe( - 'null' - ); }); it('should have an execution context', async () => { const {page} = await getTestState(); @@ -106,4 +106,17 @@ describe('Workers', function () { const errorLog = await errorPromise; expect(errorLog.message).toContain('this is my error'); }); + + it('can be closed', async () => { + const {page, server} = await getTestState(); + + await Promise.all([ + waitEvent(page, 'workercreated'), + page.goto(server.PREFIX + '/worker/worker.html'), + ]); + const worker = page.workers()[0]!; + expect(worker?.url()).toContain('worker.js'); + + await Promise.all([waitEvent(page, 'workerdestroyed'), worker?.close()]); + }); }); |