UNPKG

selenium-webdriver

Version:

The official WebDriver JavaScript bindings from the Selenium project

432 lines (387 loc) 14 kB
// Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The SFC licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. const { BeforeRequestSent, ResponseStarted, FetchError } = require('./networkTypes') const { AddInterceptParameters } = require('./addInterceptParameters') const { ContinueResponseParameters } = require('./continueResponseParameters') const { ContinueRequestParameters } = require('./continueRequestParameters') const { ProvideResponseParameters } = require('./provideResponseParameters') const NetworkEvent = { BEFORE_REQUEST_SENT: 'network.beforeRequestSent', RESPONSE_STARTED: 'network.responseStarted', RESPONSE_COMPLETED: 'network.responseCompleted', AUTH_REQUIRED: 'network.authRequired', FETCH_ERROR: 'network.fetchError', } const CacheBehavior = Object.freeze({ DEFAULT: 'default', BYPASS: 'bypass', }) /** * Represents all commands and events of Network module. * Described in https://w3c.github.io/webdriver-bidi/#module-network. */ class Network { #callbackId = 0 #listener /** * Represents a Network object. * @constructor * @param {Driver} driver - The driver to fetch the BiDi connection. * @param {Array} browsingContextIds - An array of browsing context IDs that the network events will be subscribed to. */ constructor(driver, browsingContextIds) { this._driver = driver this._browsingContextIds = browsingContextIds this.#listener = new Map() this.#listener.set(NetworkEvent.AUTH_REQUIRED, new Map()) this.#listener.set(NetworkEvent.BEFORE_REQUEST_SENT, new Map()) this.#listener.set(NetworkEvent.FETCH_ERROR, new Map()) this.#listener.set(NetworkEvent.RESPONSE_STARTED, new Map()) this.#listener.set(NetworkEvent.RESPONSE_COMPLETED, new Map()) } addCallback(eventType, callback) { const id = ++this.#callbackId const eventCallbackMap = this.#listener.get(eventType) eventCallbackMap.set(id, callback) return id } removeCallback(id) { let hasId = false for (const [, callbacks] of this.#listener) { if (callbacks.has(id)) { callbacks.delete(id) hasId = true } } if (!hasId) { throw Error(`Callback with id ${id} not found`) } } invokeCallbacks(eventType, data) { const callbacks = this.#listener.get(eventType) if (callbacks) { for (const [, callback] of callbacks) { callback(data) } } } async init() { this.bidi = await this._driver.getBidi() } /** * Subscribes to the 'network.beforeRequestSent' event and handles it with the provided callback. * * @param {Function} callback - The callback function to handle the event. * @returns {Promise<void>} - A promise that resolves when the subscription is successful. */ async beforeRequestSent(callback) { await this.subscribeAndHandleEvent('network.beforeRequestSent', callback) } /** * Subscribes to the 'network.responseStarted' event and handles it with the provided callback. * * @param {Function} callback - The callback function to handle the event. * @returns {Promise<void>} - A promise that resolves when the subscription is successful. */ async responseStarted(callback) { await this.subscribeAndHandleEvent('network.responseStarted', callback) } /** * Subscribes to the 'network.responseCompleted' event and handles it with the provided callback. * * @param {Function} callback - The callback function to handle the event. * @returns {Promise<void>} - A promise that resolves when the subscription is successful. */ async responseCompleted(callback) { await this.subscribeAndHandleEvent('network.responseCompleted', callback) } /** * Subscribes to the 'network.authRequired' event and handles it with the provided callback. * * @param {Function} callback - The callback function to handle the event. * @returns {Promise<number>} - A promise that resolves when the subscription is successful. */ async authRequired(callback) { return await this.subscribeAndHandleEvent('network.authRequired', callback) } /** * Subscribes to the 'network.fetchError' event and handles it with the provided callback. * * @param {Function} callback - The callback function to handle the event. * @returns {Promise<void>} - A promise that resolves when the subscription is successful. */ async fetchError(callback) { await this.subscribeAndHandleEvent('network.fetchError', callback) } async subscribeAndHandleEvent(eventType, callback) { if (this._browsingContextIds != null) { await this.bidi.subscribe(eventType, this._browsingContextIds) } else { await this.bidi.subscribe(eventType) } let id = this.addCallback(eventType, callback) this.ws = await this.bidi.socket this.ws.on('message', (event) => { const { params } = JSON.parse(Buffer.from(event.toString())) if (params) { let response = null if ('initiator' in params) { response = new BeforeRequestSent( params.context, params.navigation, params.redirectCount, params.request, params.timestamp, params.initiator, ) } else if ('response' in params) { response = new ResponseStarted( params.context, params.navigation, params.redirectCount, params.request, params.timestamp, params.response, ) } else if ('errorText' in params) { response = new FetchError( params.context, params.navigation, params.redirectCount, params.request, params.timestamp, params.errorText, ) } this.invokeCallbacks(eventType, response) } }) return id } /** * Adds a network intercept. * * @param {AddInterceptParameters} params - The parameters for the network intercept. * @returns {Promise<string>} - A promise that resolves to the added intercept's id. * @throws {Error} - If params is not an instance of AddInterceptParameters. */ async addIntercept(params) { if (!(params instanceof AddInterceptParameters)) { throw new Error(`Params must be an instance of AddInterceptParameters. Received:'${params}'`) } const command = { method: 'network.addIntercept', params: Object.fromEntries(params.asMap()), } let response = await this.bidi.send(command) return response.result.intercept } /** * Removes an intercept. * * @param {string} interceptId - The ID of the intercept to be removed. * @returns {Promise<void>} - A promise that resolves when the intercept is successfully removed. */ async removeIntercept(interceptId) { const command = { method: 'network.removeIntercept', params: { intercept: interceptId }, } await this.bidi.send(command) } /** * Continues the network request with authentication credentials. * @param {string} requestId - The ID of the request to continue. * @param {string} username - The username for authentication. * @param {string} password - The password for authentication. * @returns {Promise<void>} - A promise that resolves when the command is sent. */ async continueWithAuth(requestId, username, password) { const command = { method: 'network.continueWithAuth', params: { request: requestId.toString(), action: 'provideCredentials', credentials: { type: 'password', username: username, password: password, }, }, } await this.bidi.send(command) } /** * Fails a network request. * * @param {number} requestId - The ID of the request to fail. * @returns {Promise<void>} - A promise that resolves when the command is sent. */ async failRequest(requestId) { const command = { method: 'network.failRequest', params: { request: requestId.toString(), }, } await this.bidi.send(command) } /** * Continues the network request with authentication but without providing credentials. * @param {string} requestId - The ID of the request to continue with authentication. * @returns {Promise<void>} - A promise that resolves when the command is sent. */ async continueWithAuthNoCredentials(requestId) { const command = { method: 'network.continueWithAuth', params: { request: requestId.toString(), action: 'default', }, } await this.bidi.send(command) } /** * Cancels the authentication for a specific request. * * @param {string} requestId - The ID of the request to cancel authentication for. * @returns {Promise<void>} - A promise that resolves when the command is sent. */ async cancelAuth(requestId) { const command = { method: 'network.continueWithAuth', params: { request: requestId.toString(), action: 'cancel', }, } await this.bidi.send(command) } /** * Continues the network request with the provided parameters. * * @param {ContinueRequestParameters} params - The parameters for continuing the request. * @throws {Error} If params is not an instance of ContinueRequestParameters. * @returns {Promise<void>} A promise that resolves when the command is sent. */ async continueRequest(params) { if (!(params instanceof ContinueRequestParameters)) { throw new Error(`Params must be an instance of ContinueRequestParameters. Received:'${params}'`) } const command = { method: 'network.continueRequest', params: Object.fromEntries(params.asMap()), } await this.bidi.send(command) } /** * Continues the network response with the given parameters. * * @param {ContinueResponseParameters} params - The parameters for continuing the response. * @throws {Error} If params is not an instance of ContinueResponseParameters. * @returns {Promise<void>} A promise that resolves when the command is sent. */ async continueResponse(params) { if (!(params instanceof ContinueResponseParameters)) { throw new Error(`Params must be an instance of ContinueResponseParameters. Received:'${params}'`) } const command = { method: 'network.continueResponse', params: Object.fromEntries(params.asMap()), } await this.bidi.send(command) } /** * Provides a response for the network. * * @param {ProvideResponseParameters} params - The parameters for providing the response. * @throws {Error} If params is not an instance of ProvideResponseParameters. * @returns {Promise<void>} A promise that resolves when the command is sent. */ async provideResponse(params) { if (!(params instanceof ProvideResponseParameters)) { throw new Error(`Params must be an instance of ProvideResponseParameters. Received:'${params}'`) } const command = { method: 'network.provideResponse', params: Object.fromEntries(params.asMap()), } await this.bidi.send(command) } /** * Sets the cache behavior for network requests. * * @param {string} behavior - The cache behavior ("default" or "bypass") * @param {Array<string>} [contexts] - Optional array of browsing context IDs * @returns {Promise<void>} A promise that resolves when the cache behavior is set * @throws {Error} If behavior is invalid or context IDs are invalid */ async setCacheBehavior(behavior, contexts = null) { if (!Object.values(CacheBehavior).includes(behavior)) { throw new Error(`Cache behavior must be either "${CacheBehavior.DEFAULT}" or "${CacheBehavior.BYPASS}"`) } const command = { method: 'network.setCacheBehavior', params: { cacheBehavior: behavior, }, } if (contexts !== null) { if ( !Array.isArray(contexts) || contexts.length === 0 || contexts.some((c) => typeof c !== 'string' || c.trim() === '') ) { throw new Error('Contexts must be an array of non-empty strings') } command.params.contexts = contexts } await this.bidi.send(command) } /** * Unsubscribes from network events for all browsing contexts. * @returns {Promise<void>} A promise that resolves when the network connection is closed. */ async close() { if ( this._browsingContextIds !== null && this._browsingContextIds !== undefined && this._browsingContextIds.length > 0 ) { await this.bidi.unsubscribe( 'network.beforeRequestSent', 'network.responseStarted', 'network.responseCompleted', 'network.authRequired', this._browsingContextIds, ) } else { await this.bidi.unsubscribe( 'network.beforeRequestSent', 'network.responseStarted', 'network.responseCompleted', 'network.authRequired', ) } } } async function getNetworkInstance(driver, browsingContextIds = null) { let instance = new Network(driver, browsingContextIds) await instance.init() return instance } module.exports = { Network: getNetworkInstance, CacheBehavior }