diff --git a/src/common/BrowserConnector.ts b/src/common/BrowserConnector.ts index b3f2b3ba84664..d9ede04f2ec5c 100644 --- a/src/common/BrowserConnector.ts +++ b/src/common/BrowserConnector.ts @@ -14,18 +14,18 @@ * limitations under the License. */ -import { ConnectionTransport } from './ConnectionTransport.js'; +import { debugError } from '../common/helper.js'; +import { isNode } from '../environment.js'; +import { assert } from './assert.js'; import { Browser, - TargetFilterCallback, IsPageTargetCallback, + TargetFilterCallback, } from './Browser.js'; -import { assert } from './assert.js'; -import { debugError } from '../common/helper.js'; import { Connection } from './Connection.js'; -import { Viewport } from './PuppeteerViewport.js'; -import { isNode } from '../environment.js'; +import { ConnectionTransport } from './ConnectionTransport.js'; import { getFetch } from './fetch.js'; +import { Viewport } from './PuppeteerViewport.js'; /** * Generic browser options that can be passed when launching any browser or when diff --git a/src/common/DOMWorld.ts b/src/common/DOMWorld.ts index 51de8de2c5ec4..5558f9d8205f1 100644 --- a/src/common/DOMWorld.ts +++ b/src/common/DOMWorld.ts @@ -14,30 +14,29 @@ * limitations under the License. */ +import { Protocol } from 'devtools-protocol'; import { assert } from './assert.js'; -import { helper, debugError } from './helper.js'; -import { - LifecycleWatcher, - PuppeteerLifeCycleEvent, -} from './LifecycleWatcher.js'; +import { CDPSession } from './Connection.js'; import { TimeoutError } from './Errors.js'; -import { JSHandle, ElementHandle } from './JSHandle.js'; -import { ExecutionContext } from './ExecutionContext.js'; -import { TimeoutSettings } from './TimeoutSettings.js'; -import { MouseButton } from './Input.js'; -import { FrameManager, Frame } from './FrameManager.js'; -import { getQueryHandlerAndSelector } from './QueryHandler.js'; import { - SerializableOrJSHandle, - EvaluateHandleFn, - WrapElementHandle, EvaluateFn, EvaluateFnReturnType, + EvaluateHandleFn, + SerializableOrJSHandle, UnwrapPromiseLike, + WrapElementHandle, } from './EvalTypes.js'; -import { isNode } from '../environment.js'; -import { Protocol } from 'devtools-protocol'; -import { CDPSession } from './Connection.js'; +import { ExecutionContext } from './ExecutionContext.js'; +import { Frame, FrameManager } from './FrameManager.js'; +import { debugError, helper } from './helper.js'; +import { MouseButton } from './Input.js'; +import { ElementHandle, JSHandle } from './JSHandle.js'; +import { + LifecycleWatcher, + PuppeteerLifeCycleEvent, +} from './LifecycleWatcher.js'; +import { getQueryHandlerAndSelector } from './QueryHandler.js'; +import { TimeoutSettings } from './TimeoutSettings.js'; // predicateQueryHandler and checkWaitForOptions are declared here so that // TypeScript knows about them when used in the predicate function below. @@ -330,13 +329,18 @@ export class DOMWorld { } if (path !== null) { - if (!isNode) { - throw new Error( - 'Cannot pass a filepath to addScriptTag in the browser environment.' - ); + let fs; + try { + fs = (await import('fs')).promises; + } catch (error) { + if (error instanceof TypeError) { + throw new Error( + 'Can only pass a filepath to addScriptTag in a Node-like environment.' + ); + } + throw error; } - const fs = await import('fs'); - let contents = await fs.promises.readFile(path, 'utf8'); + let contents = await fs.readFile(path, 'utf8'); contents += '//# sourceURL=' + path.replace(/\n/g, ''); const context = await this.executionContext(); const handle = await context.evaluateHandle( @@ -437,13 +441,19 @@ export class DOMWorld { } if (path !== null) { - if (!isNode) { - throw new Error( - 'Cannot pass a filepath to addStyleTag in the browser environment.' - ); + let fs: typeof import('fs').promises; + try { + fs = (await import('fs')).promises; + } catch (error) { + if (error instanceof TypeError) { + throw new Error( + 'Cannot pass a filepath to addStyleTag in the browser environment.' + ); + } + throw error; } - const fs = await import('fs'); - let contents = await fs.promises.readFile(path, 'utf8'); + + let contents = await fs.readFile(path, 'utf8'); contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/'; const context = await this.executionContext(); const handle = await context.evaluateHandle(addStyleContent, contents); diff --git a/src/common/JSHandle.ts b/src/common/JSHandle.ts index 1e143c4a7cb7a..d28532e9f7c41 100644 --- a/src/common/JSHandle.ts +++ b/src/common/JSHandle.ts @@ -14,25 +14,24 @@ * limitations under the License. */ +import { Protocol } from 'devtools-protocol'; import { assert } from './assert.js'; -import { helper, debugError } from './helper.js'; -import { ExecutionContext } from './ExecutionContext.js'; -import { Page, ScreenshotOptions } from './Page.js'; import { CDPSession } from './Connection.js'; -import { KeyInput } from './USKeyboardLayout.js'; -import { FrameManager, Frame } from './FrameManager.js'; -import { getQueryHandlerAndSelector } from './QueryHandler.js'; -import { Protocol } from 'devtools-protocol'; import { EvaluateFn, - SerializableOrJSHandle, EvaluateFnReturnType, EvaluateHandleFn, - WrapElementHandle, + SerializableOrJSHandle, UnwrapPromiseLike, + WrapElementHandle, } from './EvalTypes.js'; -import { isNode } from '../environment.js'; +import { ExecutionContext } from './ExecutionContext.js'; +import { Frame, FrameManager } from './FrameManager.js'; +import { debugError, helper } from './helper.js'; import { MouseButton } from './Input.js'; +import { Page, ScreenshotOptions } from './Page.js'; +import { getQueryHandlerAndSelector } from './QueryHandler.js'; +import { KeyInput } from './USKeyboardLayout.js'; /** * @public @@ -822,17 +821,18 @@ export class ElementHandle< 'Multiple file uploads only work with ' ); - if (!isNode) { - throw new Error( - `JSHandle#uploadFile can only be used in Node environments.` - ); - } - /* - This import is only needed for `uploadFile`, so keep it scoped here to - avoid paying the cost unnecessarily. - */ - const path = await import('path'); // Locate all files and confirm that they exist. + let path: typeof import('path'); + try { + path = await import('path'); + } catch (error) { + if (error instanceof TypeError) { + throw new Error( + `JSHandle#uploadFile can only be used in Node-like environments.` + ); + } + throw error; + } const files = filePaths.map((filePath) => { if (path.isAbsolute(filePath)) { return filePath; diff --git a/src/common/Page.ts b/src/common/Page.ts index c47066a5d9c8b..c01c6beaf0179 100644 --- a/src/common/Page.ts +++ b/src/common/Page.ts @@ -16,7 +16,6 @@ import { Protocol } from 'devtools-protocol'; import type { Readable } from 'stream'; -import { isNode } from '../environment.js'; import { Accessibility } from './Accessibility.js'; import { assert, assertNever } from './assert.js'; import { Browser, BrowserContext } from './Browser.js'; @@ -2825,13 +2824,17 @@ export class Page extends EventEmitter { : Buffer.from(result.data, 'base64'); if (options.path) { - if (!isNode) { - throw new Error( - 'Screenshots can only be written to a file path in a Node environment.' - ); + try { + const fs = (await import('fs')).promises; + await fs.writeFile(options.path, buffer); + } catch (error) { + if (error instanceof TypeError) { + throw new Error( + 'Screenshots can only be written to a file path in a Node-like environment.' + ); + } + throw error; } - const fs = (await import('fs')).promises; - await fs.writeFile(options.path, buffer); } return buffer; diff --git a/src/common/fetch.ts b/src/common/fetch.ts index 8f41553b04ad8..c0ca7e3baf02c 100644 --- a/src/common/fetch.ts +++ b/src/common/fetch.ts @@ -14,9 +14,7 @@ * limitations under the License. */ -import { isNode } from '../environment.js'; - /* Use the global version if we're in the browser, else load the node-fetch module. */ export const getFetch = async (): Promise => { - return isNode ? (await import('cross-fetch')).fetch : globalThis.fetch; + return globalThis.fetch || (await import('cross-fetch')).fetch; }; diff --git a/src/common/helper.ts b/src/common/helper.ts index bc57e582c87a0..08a57483b3660 100644 --- a/src/common/helper.ts +++ b/src/common/helper.ts @@ -14,15 +14,14 @@ * limitations under the License. */ +import { Protocol } from 'devtools-protocol'; import type { Readable } from 'stream'; - -import { TimeoutError } from './Errors.js'; -import { debug } from './Debug.js'; +import { isNode } from '../environment.js'; +import { assert } from './assert.js'; import { CDPSession } from './Connection.js'; -import { Protocol } from 'devtools-protocol'; +import { debug } from './Debug.js'; +import { TimeoutError } from './Errors.js'; import { CommonEventEmitter } from './EventEmitter.js'; -import { assert } from './assert.js'; -import { isNode } from '../environment.js'; export const debugError = debug('puppeteer:error'); @@ -318,31 +317,34 @@ async function getReadableAsBuffer( readable: Readable, path?: string ): Promise { - if (!isNode && path) { - throw new Error('Cannot write to a path outside of Node.js environment.'); - } - - const fs = isNode ? (await import('fs')).promises : null; - - let fileHandle: import('fs').promises.FileHandle | undefined; - - if (path && fs) { - fileHandle = await fs.open(path, 'w'); - } const buffers = []; - for await (const chunk of readable) { - buffers.push(chunk); - if (fileHandle && fs) { - await fs.writeFile(fileHandle, chunk); + if (path) { + let fs: typeof import('fs').promises; + try { + fs = (await import('fs')).promises; + } catch (error) { + if (error instanceof TypeError) { + throw new Error( + 'Cannot write to a path outside of a Node-like environment.' + ); + } + throw error; + } + const fileHandle = await fs.open(path, 'w+'); + for await (const chunk of readable) { + buffers.push(chunk); + await fileHandle.writeFile(chunk); + } + await fileHandle.close(); + } else { + for await (const chunk of readable) { + buffers.push(chunk); } } - - if (path && fileHandle) await fileHandle.close(); - let resultBuffer = null; try { - resultBuffer = Buffer.concat(buffers); - } finally { - return resultBuffer; + return Buffer.concat(buffers); + } catch (error) { + return null; } } @@ -350,8 +352,8 @@ async function getReadableFromProtocolStream( client: CDPSession, handle: string ): Promise { - // TODO: - // This restriction can be lifted once https://github.com/nodejs/node/pull/39062 has landed + // TODO: Once Node 18 becomes the lowest supported version, we can migrate to + // ReadableStream. if (!isNode) { throw new Error('Cannot create a stream outside of Node.js environment.'); } diff --git a/src/node-puppeteer-core.ts b/src/node-puppeteer-core.ts index 976dfd5715c3c..23269bbc31814 100644 --- a/src/node-puppeteer-core.ts +++ b/src/node-puppeteer-core.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { initializePuppeteerNode } from './initialize-node.js'; import { isNode } from './environment.js'; +import { initializePuppeteerNode } from './initialize-node.js'; if (!isNode) { throw new Error('Cannot run puppeteer-core outside of Node.js'); diff --git a/src/node.ts b/src/node.ts index 714f8c2b94875..b996436b9d54f 100644 --- a/src/node.ts +++ b/src/node.ts @@ -14,10 +14,12 @@ * limitations under the License. */ -import { initializePuppeteerNode } from './initialize-node.js'; import { isNode } from './environment.js'; +import { initializePuppeteerNode } from './initialize-node.js'; if (!isNode) { throw new Error('Trying to run Puppeteer-Node in a web environment.'); } -export default initializePuppeteerNode('puppeteer'); + +const puppeteer = initializePuppeteerNode('puppeteer'); +export default puppeteer; diff --git a/src/web.ts b/src/web.ts index a48a5cf14d61c..777f9ae0c3a41 100644 --- a/src/web.ts +++ b/src/web.ts @@ -14,11 +14,12 @@ * limitations under the License. */ -import { initializePuppeteerWeb } from './initialize-web.js'; import { isNode } from './environment.js'; +import { initializePuppeteerWeb } from './initialize-web.js'; if (isNode) { throw new Error('Trying to run Puppeteer-Web in a Node environment'); } -export default initializePuppeteerWeb('puppeteer'); +const puppeteer = initializePuppeteerWeb('puppeteer'); +export default puppeteer;