webdriverio
Advanced tools
Comparing version 9.0.0-alpha.64 to 9.0.0-alpha.78
@@ -1,7 +0,4 @@ | ||
import type { CDPSession } from 'puppeteer-core'; | ||
import type { Mock } from '../../types.js'; | ||
import type { MockFilterOptions } from '../../utils/interception/types.js'; | ||
import type Interception from '../../utils/interception/index.js'; | ||
export declare const SESSION_MOCKS: Record<string, Set<Interception>>; | ||
export declare const CDP_SESSIONS: Record<string, CDPSession>; | ||
import type { MockOptions } from '../../utils/interception/types.js'; | ||
import WebDriverInterception from '../../utils/interception/index.js'; | ||
export declare const SESSION_MOCKS: Record<string, Set<WebDriverInterception>>; | ||
/** | ||
@@ -113,3 +110,3 @@ * Mock the response of a request. You can define a mock based on a matching | ||
*/ | ||
export declare function mock(this: WebdriverIO.Browser, url: string | RegExp, filterOptions?: MockFilterOptions): Promise<Mock>; | ||
export declare function mock(this: WebdriverIO.Browser, url: string, filterOptions?: MockOptions): Promise<WebdriverIO.Mock>; | ||
//# sourceMappingURL=mock.d.ts.map |
@@ -1,6 +0,4 @@ | ||
import DevtoolsNetworkInterception from '../../utils/interception/devtools.js'; | ||
import WebDriverNetworkInterception from '../../utils/interception/webdriver.js'; | ||
import { getBrowserObject } from '@wdio/utils'; | ||
import WebDriverInterception from '../../utils/interception/index.js'; | ||
export const SESSION_MOCKS = {}; | ||
export const CDP_SESSIONS = {}; | ||
/** | ||
@@ -113,9 +111,5 @@ * Mock the response of a request. You can define a mock based on a matching | ||
export async function mock(url, filterOptions) { | ||
const NetworkInterception = this.isSauce ? WebDriverNetworkInterception : DevtoolsNetworkInterception; | ||
if (!this.isSauce) { | ||
await this.getPuppeteer(); | ||
if (!this.isBidi) { | ||
throw new Error('Mocking is only supported when running tests using WebDriver Bidi'); | ||
} | ||
if (!this.puppeteer) { | ||
throw new Error('No Puppeteer connection could be established which is required to use this command'); | ||
} | ||
const browser = getBrowserObject(this); | ||
@@ -126,38 +120,5 @@ const handle = await browser.getWindowHandle(); | ||
} | ||
/** | ||
* enable network Mocking if not already | ||
*/ | ||
if (SESSION_MOCKS[handle].size === 0 && !this.isSauce) { | ||
const pages = await this.puppeteer.pages(); | ||
/** | ||
* get active page | ||
*/ | ||
let page; | ||
for (let i = 0; i < pages.length && !page; i++) { | ||
const isHidden = await pages[i].evaluate(() => document.hidden); | ||
if (!isHidden) { | ||
page = pages[i]; | ||
} | ||
} | ||
/** | ||
* fallback to the first page | ||
*/ | ||
if (!page) { | ||
page = pages[0]; | ||
} | ||
const client = CDP_SESSIONS[handle] = await page.target().createCDPSession(); | ||
await client.send('Fetch.enable', { | ||
patterns: [{ requestStage: 'Request' }, { requestStage: 'Response' }] | ||
}); | ||
client.on('Fetch.requestPaused', | ||
// @ts-expect-error fix me | ||
NetworkInterception | ||
.handleRequestInterception(client, SESSION_MOCKS[handle])); | ||
} | ||
const networkInterception = new NetworkInterception(url, filterOptions, browser); | ||
const networkInterception = await WebDriverInterception.initiate(url, filterOptions || {}, this); | ||
SESSION_MOCKS[handle].add(networkInterception); | ||
if (this.isSauce) { | ||
await networkInterception.init(); | ||
} | ||
return networkInterception; | ||
} |
@@ -1,2 +0,2 @@ | ||
import type { ThrottleOptions } from '../../utils/interception/types.js'; | ||
import type { ThrottleOptions } from '../../types.js'; | ||
/** | ||
@@ -3,0 +3,0 @@ * @deprecated use `browser.throttleNetwork` instead |
@@ -1,3 +0,3 @@ | ||
import type { ThrottleOptions } from '../../utils/interception/types.js'; | ||
import type { ThrottleOptions } from '../../types.js'; | ||
export declare function throttleNetwork(this: WebdriverIO.Browser, params: ThrottleOptions): Promise<null>; | ||
//# sourceMappingURL=throttleNetwork.d.ts.map |
@@ -85,5 +85,9 @@ import { ELEMENT_KEY } from 'webdriver'; | ||
/** | ||
* verify that shadow elements captured by the shadow root manager is still attached to the DOM | ||
*/ | ||
const elemsWithShadowRootAndIdVerified = (await Promise.all(elemsWithShadowRootAndId.map(([elemId, elem]) => (browser.execute((elem) => elem.tagName, elem).then(() => [elemId, elem], () => undefined))))).filter(Boolean); | ||
/** | ||
* then get the HTML of the element and its shadow roots | ||
*/ | ||
const { html, shadowElementIdsFound } = await browser.execute(getHTMLShadowScript, { [ELEMENT_KEY]: this.elementId }, includeSelectorTag, elemsWithShadowRootAndId); | ||
const { html, shadowElementIdsFound } = await browser.execute(getHTMLShadowScript, { [ELEMENT_KEY]: this.elementId }, includeSelectorTag, elemsWithShadowRootAndIdVerified); | ||
const $ = load(html); | ||
@@ -90,0 +94,0 @@ /** |
/// <reference types="node" resolution-mode="require"/> | ||
/// <reference types="node" resolution-mode="require"/> | ||
import type { EventEmitter } from 'node:events'; | ||
import type { SessionFlags, AttachOptions as WebDriverAttachOptions, BidiHandler, BidiEventHandler } from 'webdriver'; | ||
import type { Options, Capabilities, FunctionProperties, ThenArg } from '@wdio/types'; | ||
import type { Options, Capabilities, ThenArg } from '@wdio/types'; | ||
import type { ElementReference, ProtocolCommands } from '@wdio/protocols'; | ||
@@ -10,5 +9,4 @@ import type { Browser as PuppeteerBrowser } from 'puppeteer-core'; | ||
import type * as ElementCommands from './commands/element.js'; | ||
import type DevtoolsInterception from './utils/interception/devtools.js'; | ||
import type { Matches, ErrorReason } from './utils/interception/types.js'; | ||
import type { Button, ButtonNames } from './utils/actions/pointer.js'; | ||
import type WebDriverInterception from './utils/interception/index.js'; | ||
export * from './utils/interception/types.js'; | ||
@@ -387,34 +385,2 @@ export type RemoteOptions = Options.WebdriverIO & Omit<Options.Testrunner, 'capabilities' | 'rootDir'>; | ||
}; | ||
/** | ||
* WebdriverIO Mock definition | ||
*/ | ||
interface RequestEvent { | ||
requestId: number; | ||
request: Matches; | ||
responseStatusCode: number; | ||
responseHeaders: Record<string, string>; | ||
} | ||
interface MatchEvent extends Matches { | ||
mockedResponse?: string | Buffer; | ||
} | ||
interface OverwriteEvent { | ||
requestId: number; | ||
responseCode: number; | ||
responseHeaders: Record<string, string>; | ||
body?: string | Record<string, any>; | ||
} | ||
interface FailEvent { | ||
requestId: number; | ||
errorReason: ErrorReason; | ||
} | ||
interface MockFunctions extends Omit<FunctionProperties<DevtoolsInterception>, 'on'> { | ||
on(event: 'request', callback: (request: RequestEvent) => void): Mock; | ||
on(event: 'match', callback: (match: MatchEvent) => void): Mock; | ||
on(event: 'continue', callback: (requestId: number) => void): Mock; | ||
on(event: 'overwrite', callback: (response: OverwriteEvent) => void): Mock; | ||
on(event: 'fail', callback: (error: FailEvent) => void): Mock; | ||
} | ||
type MockProperties = Pick<DevtoolsInterception, 'calls'>; | ||
export interface Mock extends MockFunctions, MockProperties { | ||
} | ||
export interface AttachOptions extends Omit<WebDriverAttachOptions, 'capabilities'> { | ||
@@ -425,2 +391,10 @@ options: Options.WebdriverIO; | ||
} | ||
export type ThrottlePreset = 'offline' | 'GPRS' | 'Regular2G' | 'Good2G' | 'Regular3G' | 'Good3G' | 'Regular4G' | 'DSL' | 'WiFi' | 'online'; | ||
export interface CustomThrottle { | ||
offline: boolean; | ||
downloadThroughput: number; | ||
uploadThroughput: number; | ||
latency: number; | ||
} | ||
export type ThrottleOptions = ThrottlePreset | CustomThrottle; | ||
declare global { | ||
@@ -469,4 +443,13 @@ namespace WebdriverIO { | ||
} | ||
/** | ||
* WebdriverIO Mock object | ||
* The mock object is an object that represents a network mock and contains information about | ||
* requests that were matching given url and filterOptions. It can be received using the mock command. | ||
* | ||
* @see https://webdriver.io/docs/api/mock | ||
*/ | ||
interface Mock extends WebDriverInterception { | ||
} | ||
} | ||
} | ||
//# sourceMappingURL=types.d.ts.map |
@@ -41,2 +41,3 @@ import { webdriverMonad, wrapCommand } from '@wdio/utils'; | ||
}; | ||
propertiesObject.emit = { value: this.emit.bind(this) }; | ||
const element = webdriverMonad(this.options, (client) => { | ||
@@ -62,5 +63,6 @@ const elementId = getElementFromResponse(res); | ||
} | ||
client.selector = selector || ''; | ||
if (selector) { | ||
client.selector = selector; | ||
} | ||
client.parent = this; | ||
client.emit = this.emit.bind(this); | ||
client.isReactElement = props.isReactElement; | ||
@@ -110,2 +112,3 @@ client.isShadowElement = props.isShadowElement; | ||
propertiesObject.scope = { value: 'element' }; | ||
propertiesObject.emit = { value: this.emit.bind(this) }; | ||
const element = webdriverMonad(this.options, (client) => { | ||
@@ -133,3 +136,2 @@ const elementId = getElementFromResponse(res); | ||
client.index = i; | ||
client.emit = this.emit.bind(this); | ||
client.isReactElement = props.isReactElement; | ||
@@ -136,0 +138,0 @@ client.isShadowElement = props.isShadowElement; |
@@ -185,3 +185,5 @@ /// <reference types="@wdio/globals/types" /> | ||
// Safari | ||
err.message.includes('Stale element found')); | ||
err.message.toLowerCase().includes('stale element found') || | ||
// Chrome through JS execution | ||
err.message.includes('stale element not found in the current frame')); | ||
} | ||
@@ -188,0 +190,0 @@ /** |
/// <reference types="node" resolution-mode="require"/> | ||
import EventEmitter from 'node:events'; | ||
import { type JsonCompatible } from '@wdio/types'; | ||
import { type local } from 'webdriver'; | ||
import { URLPattern } from 'urlpattern-polyfill'; | ||
import type { MockOptions, RespondWithOptions } from './types.js'; | ||
import type { WaitForOptions } from '../../types.js'; | ||
import type { MockFilterOptions, MockOverwrite, MockResponseParams, Matches, ErrorReason } from './types.js'; | ||
export default abstract class Interception extends EventEmitter { | ||
url: string | RegExp; | ||
filterOptions: MockFilterOptions; | ||
browser: WebdriverIO.Browser; | ||
abstract calls: Matches[] | Promise<Matches[]>; | ||
abstract clear(): void; | ||
abstract restore(): Promise<void>; | ||
abstract respond(overwrite: MockOverwrite, params: MockResponseParams): void; | ||
abstract respondOnce(overwrite: MockOverwrite, params: MockResponseParams): void; | ||
abstract abort(errorReason: ErrorReason, sticky: boolean): void; | ||
abstract abortOnce(errorReason: ErrorReason): void; | ||
respondOverwrites: { | ||
overwrite?: MockOverwrite; | ||
params?: MockResponseParams; | ||
sticky?: boolean; | ||
errorReason?: ErrorReason; | ||
}[]; | ||
matches: Matches[]; | ||
constructor(url: string | RegExp, filterOptions: MockFilterOptions, browser: WebdriverIO.Browser); | ||
type RespondBody = string | JsonCompatible | Buffer; | ||
/** | ||
* Network interception class based on a WebDriver Bidi implementation. | ||
* | ||
* Note: this code is executed in Node.js and in the browser, so make sure | ||
* you use primitives that work in both environments. | ||
*/ | ||
export default class WebDriverInterception { | ||
#private; | ||
private constructor(); | ||
static initiate(url: string, | ||
/** | ||
* ToDo(Christian): incorporate filterOptions | ||
*/ | ||
filterOptions: MockOptions, browser: WebdriverIO.Browser): Promise<WebDriverInterception>; | ||
/** | ||
* allows access to all requests made with given pattern | ||
*/ | ||
get calls(): local.NetworkResponseCompletedParameters[]; | ||
/** | ||
* Resets all information stored in the `mock.calls` set. | ||
*/ | ||
clear(): this; | ||
/** | ||
* Does what `mock.clear()` does and makes removes custom request overrides | ||
* and response overwrites | ||
*/ | ||
reset(): this; | ||
/** | ||
* Does everything that `mock.reset()` does, and also | ||
* removes any mocked return values or implementations. | ||
* Restored mock does not emit events and could not mock responses | ||
*/ | ||
restore(): Promise<this>; | ||
/** | ||
* Always respond with same overwrite | ||
* @param {*} overwrites payload to overwrite the response | ||
* @param {*} params additional respond parameters to overwrite | ||
* @param {boolean} once apply overwrite only once for the next request | ||
*/ | ||
respond(payload: RespondBody, params?: Omit<RespondWithOptions, 'body'>, once?: boolean): this; | ||
/** | ||
* alias for `mock.respond(…, true)` | ||
*/ | ||
respondOnce(payload: RespondBody, params?: Omit<RespondWithOptions, 'body'>): this; | ||
/** | ||
* Abort the request with an error code | ||
* @param {string} errorReason error code of the response | ||
* @param {boolean} once if request should be aborted only once for the next request | ||
*/ | ||
abort(once?: boolean): this; | ||
/** | ||
* alias for `mock.abort(true)` | ||
*/ | ||
abortOnce(): this; | ||
/** | ||
* Redirect request to another URL | ||
* @param {string} redirectUrl URL to redirect to | ||
* @param {boolean} sticky if request should be redirected for all following requests | ||
*/ | ||
redirect(redirectUrl: string, once?: boolean): this; | ||
/** | ||
* alias for `mock.redirect(…, true)` | ||
*/ | ||
redirectOnce(redirectUrl: string): this; | ||
on(event: 'request', callback: (request: local.NetworkBeforeRequestSentParameters) => void): WebDriverInterception; | ||
on(event: 'match', callback: (match: local.NetworkBeforeRequestSentParameters) => void): WebDriverInterception; | ||
on(event: 'continue', callback: (requestId: string) => void): WebDriverInterception; | ||
on(event: 'fail', callback: (requestId: string) => void): WebDriverInterception; | ||
on(event: 'overwrite', callback: (response: local.NetworkResponseCompletedParameters) => void): WebDriverInterception; | ||
waitForResponse({ timeout, interval, timeoutMsg, }?: WaitForOptions): Promise<boolean> | Promise<Promise<boolean>>; | ||
static isMatchingRequest(expectedUrl: string | RegExp, actualUrl: string): boolean; | ||
} | ||
export declare function parseUrlPattern(url: string | URLPattern): URLPattern; | ||
export {}; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -0,17 +1,278 @@ | ||
/* eslint-disable no-dupe-class-members */ | ||
import EventEmitter from 'node:events'; | ||
import { minimatch } from 'minimatch'; | ||
import logger from '@wdio/logger'; | ||
import { URLPattern } from 'urlpattern-polyfill'; | ||
import Timer from '../Timer.js'; | ||
export default class Interception extends EventEmitter { | ||
url; | ||
filterOptions; | ||
browser; | ||
respondOverwrites = []; | ||
matches = []; | ||
constructor(url, filterOptions = {}, browser) { | ||
super(); | ||
this.url = url; | ||
this.filterOptions = filterOptions; | ||
this.browser = browser; | ||
import { parseOverwrite, getPatternParam } from './utils.js'; | ||
import { SESSION_MOCKS } from '../../commands/browser/mock.js'; | ||
const log = logger('WebDriverInterception'); | ||
let hasSubscribedToEvents = false; | ||
/** | ||
* Network interception class based on a WebDriver Bidi implementation. | ||
* | ||
* Note: this code is executed in Node.js and in the browser, so make sure | ||
* you use primitives that work in both environments. | ||
*/ | ||
export default class WebDriverInterception { | ||
#pattern; | ||
#mockId; | ||
#browser; | ||
#emitter = new EventEmitter(); | ||
#restored = false; | ||
#respondOverwrites = []; | ||
#calls = []; | ||
constructor(pattern, mockId, browser) { | ||
this.#pattern = pattern; | ||
this.#mockId = mockId; | ||
this.#browser = browser; | ||
/** | ||
* attach network listener to this mock | ||
*/ | ||
browser.on('network.beforeRequestSent', this.#handleBeforeRequestSent.bind(this)); | ||
browser.on('network.responseStarted', this.#handleResponseStarted.bind(this)); | ||
} | ||
waitForResponse({ timeout = this.browser.options.waitforTimeout, interval = this.browser.options.waitforInterval, timeoutMsg, } = {}) { | ||
static async initiate(url, | ||
/** | ||
* ToDo(Christian): incorporate filterOptions | ||
*/ | ||
filterOptions, browser) { | ||
const pattern = parseUrlPattern(url); | ||
if (!hasSubscribedToEvents) { | ||
await browser.sessionSubscribe({ | ||
events: [ | ||
'network.beforeRequestSent', | ||
'network.responseStarted' | ||
] | ||
}); | ||
log.info('subscribed to network events'); | ||
hasSubscribedToEvents = true; | ||
} | ||
/** | ||
* register network intercept | ||
*/ | ||
const interception = await browser.networkAddIntercept({ | ||
phases: ['beforeRequestSent', 'responseStarted'], | ||
urlPatterns: [{ | ||
type: 'pattern', | ||
protocol: getPatternParam(pattern, 'protocol'), | ||
hostname: getPatternParam(pattern, 'hostname'), | ||
pathname: getPatternParam(pattern, 'pathname'), | ||
port: getPatternParam(pattern, 'port'), | ||
search: getPatternParam(pattern, 'search') | ||
}] | ||
}); | ||
return new WebDriverInterception(pattern, interception.intercept, browser); | ||
} | ||
#handleBeforeRequestSent(request) { | ||
/** | ||
* don't do anything if: | ||
* - request is not blocked | ||
* - request is not matching the pattern, e.g. a different mock is responsible for this request | ||
*/ | ||
if (!this.#isRequestMatching(request)) { | ||
return; | ||
} | ||
/** | ||
* continue the request without modifications, if: | ||
*/ | ||
const continueRequest = ( | ||
/** | ||
* - mock has no request/respond overwrites | ||
* - no request modifications are set | ||
* - no errorReason is set | ||
* - no requestWith is set | ||
*/ | ||
this.#respondOverwrites.length === 0 || | ||
(!this.#respondOverwrites[0].abort && | ||
!this.#respondOverwrites[0].requestWith)); | ||
/** | ||
* check if request matches the mock url | ||
*/ | ||
if (continueRequest) { | ||
this.#emitter.emit('continue', request.request.request); | ||
return this.#browser.networkContinueRequest({ | ||
request: request.request.request | ||
}); | ||
} | ||
this.#emitter.emit('request', request); | ||
const { requestWith, abort } = this.#respondOverwrites[0].once | ||
? this.#respondOverwrites.shift() || {} | ||
: this.#respondOverwrites[0]; | ||
/** | ||
* check if should abort the request | ||
*/ | ||
if (abort) { | ||
this.#emitter.emit('fail', request.request.request); | ||
return this.#browser.networkFailRequest({ request: request.request.request }); | ||
} | ||
/** | ||
* continue request (possibly with overwrites) | ||
*/ | ||
if (requestWith) { | ||
this.#emitter.emit('overwrite', request); | ||
return this.#browser.networkContinueRequest({ | ||
request: request.request.request, | ||
...parseOverwrite(requestWith, request) | ||
}); | ||
} | ||
throw new Error('This should never happen'); | ||
} | ||
#handleResponseStarted(request) { | ||
/** | ||
* don't do anything if: | ||
* - request is not blocked | ||
* - request is not matching the pattern, e.g. a different mock is responsible for this request | ||
*/ | ||
if (!this.#isRequestMatching(request)) { | ||
return; | ||
} | ||
/** | ||
* continue the request without modifications, if: | ||
*/ | ||
const continueRequest = ( | ||
/** | ||
* - mock has no request/respond overwrites | ||
* - no request modifications are set, e.g. no overwrite is set | ||
*/ | ||
this.#respondOverwrites.length === 0 || | ||
!this.#respondOverwrites[0].overwrite); | ||
/** | ||
* check if request matches the mock url | ||
*/ | ||
if (continueRequest) { | ||
this.#emitter.emit('continue', request.request.request); | ||
return this.#browser.networkProvideResponse({ | ||
request: request.request.request | ||
}); | ||
} | ||
this.#calls.push(request); | ||
const { overwrite } = this.#respondOverwrites[0].once | ||
? this.#respondOverwrites.shift() || {} | ||
: this.#respondOverwrites[0]; | ||
/** | ||
* continue request (possibly with overwrites) | ||
*/ | ||
if (overwrite) { | ||
this.#emitter.emit('overwrite', request); | ||
return this.#browser.networkProvideResponse({ | ||
request: request.request.request, | ||
...parseOverwrite(overwrite, request) | ||
}); | ||
} | ||
/** | ||
* continue request as is | ||
*/ | ||
this.#emitter.emit('continue', request.request.request); | ||
return this.#browser.networkProvideResponse({ | ||
request: request.request.request | ||
}); | ||
} | ||
#isRequestMatching(request) { | ||
return request.isBlocked && this.#pattern && this.#pattern.test(request.request.url); | ||
} | ||
/** | ||
* allows access to all requests made with given pattern | ||
*/ | ||
get calls() { | ||
return this.#calls; | ||
} | ||
/** | ||
* Resets all information stored in the `mock.calls` set. | ||
*/ | ||
clear() { | ||
this.#calls = []; | ||
return this; | ||
} | ||
/** | ||
* Does what `mock.clear()` does and makes removes custom request overrides | ||
* and response overwrites | ||
*/ | ||
reset() { | ||
this.clear(); | ||
this.#respondOverwrites = []; | ||
return this; | ||
} | ||
/** | ||
* Does everything that `mock.reset()` does, and also | ||
* removes any mocked return values or implementations. | ||
* Restored mock does not emit events and could not mock responses | ||
*/ | ||
async restore() { | ||
this.reset(); | ||
this.#respondOverwrites = []; | ||
this.#restored = true; | ||
const handle = await this.#browser.getWindowHandle(); | ||
log.trace(`Restoring mock for ${handle}`); | ||
SESSION_MOCKS[handle].delete(this); | ||
if (this.#mockId) { | ||
await this.#browser.networkRemoveIntercept({ intercept: this.#mockId }); | ||
} | ||
return this; | ||
} | ||
/** | ||
* Always respond with same overwrite | ||
* @param {*} overwrites payload to overwrite the response | ||
* @param {*} params additional respond parameters to overwrite | ||
* @param {boolean} once apply overwrite only once for the next request | ||
*/ | ||
respond(payload, params = {}, once) { | ||
this.#ensureNotRestored(); | ||
const body = typeof payload === 'string' | ||
? payload | ||
: globalThis.Buffer && globalThis.Buffer.isBuffer(payload) | ||
? payload.toString('base64') | ||
: JSON.stringify(payload); | ||
const overwrite = { body, ...params }; | ||
this.#respondOverwrites.push({ overwrite, once }); | ||
return this; | ||
} | ||
/** | ||
* alias for `mock.respond(…, true)` | ||
*/ | ||
respondOnce(payload, params = {}) { | ||
return this.respond(payload, params, true); | ||
} | ||
/** | ||
* Abort the request with an error code | ||
* @param {string} errorReason error code of the response | ||
* @param {boolean} once if request should be aborted only once for the next request | ||
*/ | ||
abort(once) { | ||
this.#ensureNotRestored(); | ||
this.#respondOverwrites.push({ abort: true, once }); | ||
return this; | ||
} | ||
/** | ||
* alias for `mock.abort(true)` | ||
*/ | ||
abortOnce() { | ||
return this.abort(true); | ||
} | ||
/** | ||
* Redirect request to another URL | ||
* @param {string} redirectUrl URL to redirect to | ||
* @param {boolean} sticky if request should be redirected for all following requests | ||
*/ | ||
redirect(redirectUrl, once) { | ||
this.#ensureNotRestored(); | ||
const requestWith = { url: redirectUrl }; | ||
this.#respondOverwrites.push({ requestWith, once }); | ||
return this; | ||
} | ||
/** | ||
* alias for `mock.redirect(…, true)` | ||
*/ | ||
redirectOnce(redirectUrl) { | ||
return this.redirect(redirectUrl, true); | ||
} | ||
on(event, callback) { | ||
this.#emitter.on(event, callback); | ||
return this; | ||
} | ||
#ensureNotRestored() { | ||
if (this.#restored) { | ||
throw new Error('This can\'t be done on restored mock'); | ||
} | ||
} | ||
waitForResponse({ timeout = this.#browser.options.waitforTimeout, interval = this.#browser.options.waitforInterval, timeoutMsg, } = {}) { | ||
/*! | ||
@@ -21,6 +282,6 @@ * ensure that timeout and interval are set properly | ||
if (typeof timeout !== 'number') { | ||
timeout = this.browser.options.waitforTimeout; | ||
timeout = this.#browser.options.waitforTimeout; | ||
} | ||
if (typeof interval !== 'number') { | ||
interval = this.browser.options.waitforInterval; | ||
interval = this.#browser.options.waitforInterval; | ||
} | ||
@@ -30,3 +291,3 @@ /* istanbul ignore next */ | ||
const timer = new Timer(interval, timeout, fn, true); | ||
return this.browser.call(() => timer.catch((e) => { | ||
return this.#browser.call(() => timer.catch((e) => { | ||
if (e.message === 'timeout') { | ||
@@ -41,11 +302,22 @@ if (typeof timeoutMsg === 'string') { | ||
} | ||
static isMatchingRequest(expectedUrl, actualUrl) { | ||
if (typeof expectedUrl === 'string') { | ||
return minimatch(actualUrl, expectedUrl); | ||
} | ||
if (expectedUrl instanceof RegExp) { | ||
return Boolean(actualUrl.match(expectedUrl)); | ||
} | ||
throw new Error(`Unexpected type for mock url: ${expectedUrl}`); | ||
} | ||
export function parseUrlPattern(url) { | ||
/** | ||
* return early if it's already a URLPattern | ||
*/ | ||
if (typeof url === 'object') { | ||
return url; | ||
} | ||
/** | ||
* parse URLPattern from absolute URL | ||
*/ | ||
if (url.startsWith('http')) { | ||
return new URLPattern(url); | ||
} | ||
/** | ||
* parse URLPattern from relative URL | ||
*/ | ||
return new URLPattern({ | ||
pathname: url | ||
}); | ||
} |
@@ -1,78 +0,3 @@ | ||
/// <reference types="node" resolution-mode="require"/> | ||
import type { CDPSession } from 'puppeteer-core'; | ||
import type { JsonCompatible } from '@wdio/types'; | ||
/** | ||
* HTTP request data. (copied from the puppeteer-core package as there is currently | ||
* no way to access these types otherwise) | ||
*/ | ||
export type ResourcePriority = 'VeryLow' | 'Low' | 'Medium' | 'High' | 'VeryHigh'; | ||
export type MixedContentType = 'blockable' | 'optionally-blockable' | 'none'; | ||
export type ReferrerPolicy = 'unsafe-url' | 'no-referrer-when-downgrade' | 'no-referrer' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin'; | ||
export type ErrorReason = 'Failed' | 'Aborted' | 'TimedOut' | 'AccessDenied' | 'ConnectionClosed' | 'ConnectionReset' | 'ConnectionRefused' | 'ConnectionAborted' | 'ConnectionFailed' | 'NameNotResolved' | 'InternetDisconnected' | 'AddressUnreachable' | 'BlockedByClient' | 'BlockedByResponse'; | ||
export interface Request { | ||
/** | ||
* Request URL (without fragment). | ||
*/ | ||
url: string; | ||
/** | ||
* Fragment of the requested URL starting with hash, if present. | ||
*/ | ||
urlFragment?: string; | ||
/** | ||
* HTTP request method. | ||
*/ | ||
method: string; | ||
/** | ||
* HTTP request headers. | ||
*/ | ||
headers: Record<string, string>; | ||
/** | ||
* HTTP POST request data. | ||
*/ | ||
postData?: string; | ||
/** | ||
* True when the request has POST data. Note that postData might still be omitted when this flag is true when the data is too long. | ||
*/ | ||
hasPostData?: boolean; | ||
/** | ||
* The mixed content export type of the request. | ||
*/ | ||
mixedContentType?: MixedContentType; | ||
/** | ||
* Priority of the resource request at the time request is sent. | ||
*/ | ||
initialPriority: ResourcePriority; | ||
/** | ||
* The referrer policy of the request, as defined in https://www.w3.org/TR/referrer-policy/ | ||
*/ | ||
referrerPolicy: ReferrerPolicy; | ||
/** | ||
* Whether is loaded via link preload. | ||
*/ | ||
isLinkPreload?: boolean; | ||
} | ||
export interface Matches extends Request { | ||
/** | ||
* body response of actual resource | ||
*/ | ||
body: string | Buffer | JsonCompatible; | ||
/** | ||
* HTTP response headers. | ||
*/ | ||
responseHeaders: Record<string, string>; | ||
/** | ||
* HTTP response status code. | ||
*/ | ||
statusCode: number; | ||
} | ||
export type MockOverwriteFunction = (request: Matches, client: CDPSession) => Promise<string | Record<string, any>>; | ||
export type MockOverwrite = string | Record<string, any> | MockOverwriteFunction; | ||
export type MockResponseParams = { | ||
statusCode?: number | ((request: Matches) => number); | ||
headers?: Record<string, string> | ((request: Matches) => Record<string, string>); | ||
/** | ||
* fetch real response before responding with mocked data. Default: true | ||
*/ | ||
fetchResponse?: boolean; | ||
}; | ||
import type { local } from 'webdriver'; | ||
import type { Cookie } from '@wdio/protocols'; | ||
export type MockFilterOptions = { | ||
@@ -86,11 +11,19 @@ method?: string | ((method: string) => boolean); | ||
}; | ||
export type ErrorCode = 'Failed' | 'Aborted' | 'TimedOut' | 'AccessDenied' | 'ConnectionClosed' | 'ConnectionReset' | 'ConnectionRefused' | 'ConnectionAborted' | 'ConnectionFailed' | 'NameNotResolved' | 'InternetDisconnected' | 'AddressUnreachable' | 'BlockedByClient' | 'BlockedByResponse'; | ||
export type ThrottlePreset = 'offline' | 'GPRS' | 'Regular2G' | 'Good2G' | 'Regular3G' | 'Good3G' | 'Regular4G' | 'DSL' | 'WiFi' | 'online'; | ||
export interface CustomThrottle { | ||
offline: boolean; | ||
downloadThroughput: number; | ||
uploadThroughput: number; | ||
latency: number; | ||
type Overwrite<T, Request> = T | ((request: Request) => T); | ||
type Methods = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'PATCH' | 'OPTIONS' | 'HEAD'; | ||
export interface RequestWithOptions { | ||
body?: Overwrite<any, local.NetworkBeforeRequestSentParameters>; | ||
cookies?: Overwrite<Cookie[], local.NetworkBeforeRequestSentParameters>; | ||
headers?: Overwrite<Record<string, string>, local.NetworkBeforeRequestSentParameters>; | ||
method?: Overwrite<Methods, local.NetworkBeforeRequestSentParameters>; | ||
url?: Overwrite<string, local.NetworkBeforeRequestSentParameters>; | ||
} | ||
export type ThrottleOptions = ThrottlePreset | CustomThrottle; | ||
export interface RespondWithOptions extends Omit<RequestWithOptions, 'url' | 'method'> { | ||
statusCode?: Overwrite<number, local.NetworkResponseCompletedParameters>; | ||
} | ||
export interface MockRequestOptions { | ||
requestWith?: RequestWithOptions; | ||
} | ||
export type MockOptions = MockFilterOptions & MockRequestOptions; | ||
export {}; | ||
//# sourceMappingURL=types.d.ts.map |
@@ -8,3 +8,5 @@ import implicitWait from './implicitWait.js'; | ||
const selectors = []; | ||
//Crawl back to the browser object, and cache all selectors | ||
/** | ||
* Crawl back to the browser object, and cache all selectors | ||
*/ | ||
while (currentElement.elementId && currentElement.parent) { | ||
@@ -16,3 +18,5 @@ selectors.push({ selector: currentElement.selector, index: currentElement.index || 0 }); | ||
const length = selectors.length; | ||
// Beginning with the browser object, rechain | ||
/** | ||
* Beginning with the browser object, re-chain | ||
*/ | ||
return selectors.reduce(async (elementPromise, { selector, index }, currentIndex) => { | ||
@@ -19,0 +23,0 @@ const resolvedElement = await elementPromise; |
{ | ||
"name": "webdriverio", | ||
"description": "Next-gen browser and mobile automation test framework for Node.js", | ||
"version": "9.0.0-alpha.64+3cfecb6e4", | ||
"version": "9.0.0-alpha.78+fee2f8a88", | ||
"homepage": "https://webdriver.io", | ||
@@ -71,8 +71,8 @@ "author": "Christian Bromann <mail@bromann.dev>", | ||
"@types/node": "^20.11.30", | ||
"@wdio/config": "9.0.0-alpha.64+3cfecb6e4", | ||
"@wdio/logger": "9.0.0-alpha.64+3cfecb6e4", | ||
"@wdio/protocols": "9.0.0-alpha.64+3cfecb6e4", | ||
"@wdio/repl": "9.0.0-alpha.64+3cfecb6e4", | ||
"@wdio/types": "9.0.0-alpha.64+3cfecb6e4", | ||
"@wdio/utils": "9.0.0-alpha.64+3cfecb6e4", | ||
"@wdio/config": "9.0.0-alpha.78+fee2f8a88", | ||
"@wdio/logger": "9.0.0-alpha.78+fee2f8a88", | ||
"@wdio/protocols": "9.0.0-alpha.78+fee2f8a88", | ||
"@wdio/repl": "9.0.0-alpha.78+fee2f8a88", | ||
"@wdio/types": "9.0.0-alpha.78+fee2f8a88", | ||
"@wdio/utils": "9.0.0-alpha.78+fee2f8a88", | ||
"archiver": "^7.0.1", | ||
@@ -94,3 +94,4 @@ "aria-query": "^5.3.0", | ||
"serialize-error": "^11.0.3", | ||
"webdriver": "9.0.0-alpha.64+3cfecb6e4" | ||
"urlpattern-polyfill": "^10.0.0", | ||
"webdriver": "9.0.0-alpha.78+fee2f8a88" | ||
}, | ||
@@ -105,3 +106,3 @@ "peerDependencies": { | ||
}, | ||
"gitHead": "3cfecb6e45e7d38a1e86766d79d83822160c8e45" | ||
"gitHead": "fee2f8a88d132537795eaf144abf1a7e242f99ff" | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
639765
26
424
13892
+ Addedurlpattern-polyfill@^10.0.0