chore: migrate waitForSelector to not use rerunnable task (#19715)
This commit is contained in:
parent
24f2ccb4ca
commit
c1b9a56079
|
|
@ -26,7 +26,6 @@ import * as js from './javascript';
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
import type { Progress } from './progress';
|
import type { Progress } from './progress';
|
||||||
import { ProgressController } from './progress';
|
import { ProgressController } from './progress';
|
||||||
import type { SelectorInfo } from './selectors';
|
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
import type { TimeoutOptions } from '../common/types';
|
import type { TimeoutOptions } from '../common/types';
|
||||||
import { isUnderTest } from '../utils';
|
import { isUnderTest } from '../utils';
|
||||||
|
|
@ -1004,47 +1003,6 @@ function compensateHalfIntegerRoundingError(point: types.Point) {
|
||||||
|
|
||||||
export type SchedulableTask<T> = (injectedScript: js.JSHandle<InjectedScript>) => Promise<js.JSHandle<InjectedScriptPoll<T>>>;
|
export type SchedulableTask<T> = (injectedScript: js.JSHandle<InjectedScript>) => Promise<js.JSHandle<InjectedScriptPoll<T>>>;
|
||||||
|
|
||||||
export function waitForSelectorTask(selector: SelectorInfo, state: 'attached' | 'detached' | 'visible' | 'hidden', omitReturnValue?: boolean, root?: ElementHandle): SchedulableTask<Element | undefined> {
|
|
||||||
return injectedScript => injectedScript.evaluateHandle((injected, { parsed, strict, state, omitReturnValue, root }) => {
|
|
||||||
let lastElement: Element | undefined;
|
|
||||||
|
|
||||||
return injected.pollRaf(progress => {
|
|
||||||
const elements = injected.querySelectorAll(parsed, root || document);
|
|
||||||
let element: Element | undefined = elements[0];
|
|
||||||
const visible = element ? injected.isVisible(element) : false;
|
|
||||||
|
|
||||||
if (lastElement !== element) {
|
|
||||||
lastElement = element;
|
|
||||||
if (!element) {
|
|
||||||
progress.log(` locator did not resolve to any element`);
|
|
||||||
} else {
|
|
||||||
if (elements.length > 1) {
|
|
||||||
if (strict)
|
|
||||||
throw injected.strictModeViolationError(parsed, elements);
|
|
||||||
progress.log(` locator resolved to ${elements.length} elements. Proceeding with the first one.`);
|
|
||||||
}
|
|
||||||
progress.log(` locator resolved to ${visible ? 'visible' : 'hidden'} ${injected.previewNode(element)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasElement = !!element;
|
|
||||||
if (omitReturnValue)
|
|
||||||
element = undefined;
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case 'attached':
|
|
||||||
return hasElement ? element : progress.continuePolling;
|
|
||||||
case 'detached':
|
|
||||||
return !hasElement ? undefined : progress.continuePolling;
|
|
||||||
case 'visible':
|
|
||||||
return visible ? element : progress.continuePolling;
|
|
||||||
case 'hidden':
|
|
||||||
return !visible ? undefined : progress.continuePolling;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, { parsed: selector.parsed, strict: selector.strict, state, omitReturnValue, root });
|
|
||||||
}
|
|
||||||
|
|
||||||
function joinWithAnd(strings: string[]): string {
|
function joinWithAnd(strings: string[]): string {
|
||||||
if (strings.length < 1)
|
if (strings.length < 1)
|
||||||
return strings.join(', ');
|
return strings.join(', ');
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,9 @@ import { ManualPromise } from '../utils/manualPromise';
|
||||||
import { debugLogger } from '../common/debugLogger';
|
import { debugLogger } from '../common/debugLogger';
|
||||||
import type { CallMetadata } from './instrumentation';
|
import type { CallMetadata } from './instrumentation';
|
||||||
import { serverSideCallMetadata, SdkObject } from './instrumentation';
|
import { serverSideCallMetadata, SdkObject } from './instrumentation';
|
||||||
import { type InjectedScript } from './injected/injectedScript';
|
import type { InjectedScript, ElementStateWithoutStable, FrameExpectParams, InjectedScriptPoll, InjectedScriptProgress } from './injected/injectedScript';
|
||||||
import type { ElementStateWithoutStable, FrameExpectParams, InjectedScriptPoll, InjectedScriptProgress } from './injected/injectedScript';
|
|
||||||
import { isSessionClosedError } from './protocolError';
|
import { isSessionClosedError } from './protocolError';
|
||||||
import type { ParsedSelector } from './isomorphic/selectorParser';
|
import { type ParsedSelector, isInvalidSelectorError, splitSelectorByFrame, stringifySelector } from './isomorphic/selectorParser';
|
||||||
import { isInvalidSelectorError, splitSelectorByFrame, stringifySelector } from './isomorphic/selectorParser';
|
|
||||||
import type { SelectorInfo } from './selectors';
|
import type { SelectorInfo } from './selectors';
|
||||||
import type { ScreenshotOptions } from './screenshotter';
|
import type { ScreenshotOptions } from './screenshotter';
|
||||||
import type { InputFilesItems } from './dom';
|
import type { InputFilesItems } from './dom';
|
||||||
|
|
@ -788,7 +786,7 @@ export class Frame extends SdkObject {
|
||||||
return this._page.selectors.query(result.frame, result.info);
|
return this._page.selectors.query(result.frame, result.info);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForSelector(metadata: CallMetadata, selector: string, options: types.WaitForElementOptions & { omitReturnValue?: boolean }, scope?: dom.ElementHandle): Promise<dom.ElementHandle<Element> | null> {
|
async waitForSelector(metadata: CallMetadata, selector: string, options: types.WaitForElementOptions, scope?: dom.ElementHandle): Promise<dom.ElementHandle<Element> | null> {
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
if ((options as any).visibility)
|
if ((options as any).visibility)
|
||||||
throw new Error('options.visibility is not supported, did you mean options.state?');
|
throw new Error('options.visibility is not supported, did you mean options.state?');
|
||||||
|
|
@ -799,27 +797,45 @@ export class Frame extends SdkObject {
|
||||||
throw new Error(`state: expected one of (attached|detached|visible|hidden)`);
|
throw new Error(`state: expected one of (attached|detached|visible|hidden)`);
|
||||||
return controller.run(async progress => {
|
return controller.run(async progress => {
|
||||||
progress.log(`waiting for ${this._asLocator(selector)}${state === 'attached' ? '' : ' to be ' + state}`);
|
progress.log(`waiting for ${this._asLocator(selector)}${state === 'attached' ? '' : ' to be ' + state}`);
|
||||||
return this.retryWithProgress(progress, selector, options, async (selectorInFrame, continuePolling) => {
|
const promise = this.retryWithProgressAndTimeouts(progress, [0, 20, 50, 100, 100, 500], async continuePolling => {
|
||||||
// Be careful, |this| can be different from |frame|.
|
const resolved = await this._resolveInjectedForSelector(progress, selector, options, scope);
|
||||||
// We did not pass omitAttached, so it is non-null.
|
if (!resolved)
|
||||||
const { frame, info } = selectorInFrame!;
|
return continuePolling;
|
||||||
const actualScope = this === frame ? scope : undefined;
|
const result = await resolved.injected.evaluateHandle((injected, { info, root }) => {
|
||||||
const task = dom.waitForSelectorTask(info, state, options.omitReturnValue, actualScope);
|
const elements = injected.querySelectorAll(info.parsed, root || document);
|
||||||
const result = actualScope ? await frame._runWaitForSelectorTaskOnce(progress, stringifySelector(info.parsed), info.world, task)
|
const element: Element | undefined = elements[0];
|
||||||
: await frame._scheduleRerunnableHandleTask(progress, info.world, task);
|
const visible = element ? injected.isVisible(element) : false;
|
||||||
if (!result.asElement()) {
|
let log = '';
|
||||||
result.dispose();
|
if (elements.length > 1) {
|
||||||
return null;
|
if (info.strict)
|
||||||
|
throw injected.strictModeViolationError(info.parsed, elements);
|
||||||
|
log = ` locator resolved to ${elements.length} elements. Proceeding with the first one: ${injected.previewNode(elements[0])}`;
|
||||||
|
} else if (element) {
|
||||||
|
log = ` locator resolved to ${visible ? 'visible' : 'hidden'} ${injected.previewNode(element)}`;
|
||||||
}
|
}
|
||||||
|
return { log, element, visible, attached: !!element };
|
||||||
|
}, { info: resolved.info, root: resolved.frame === this ? scope : undefined });
|
||||||
|
const { log, visible, attached } = await result.evaluate(r => ({ log: r.log, visible: r.visible, attached: r.attached }));
|
||||||
|
if (log)
|
||||||
|
progress.log(log);
|
||||||
|
const success = { attached, detached: !attached, visible, hidden: !visible }[state];
|
||||||
|
if (!success) {
|
||||||
|
result.dispose();
|
||||||
|
return continuePolling;
|
||||||
|
}
|
||||||
|
const element = state === 'attached' || state === 'visible' ? await result.evaluateHandle(r => r.element) : null;
|
||||||
|
result.dispose();
|
||||||
|
if (!element)
|
||||||
|
return null;
|
||||||
if ((options as any).__testHookBeforeAdoptNode)
|
if ((options as any).__testHookBeforeAdoptNode)
|
||||||
await (options as any).__testHookBeforeAdoptNode();
|
await (options as any).__testHookBeforeAdoptNode();
|
||||||
const handle = result.asElement() as dom.ElementHandle<Element>;
|
|
||||||
try {
|
try {
|
||||||
return await handle._adoptTo(await frame._mainContext());
|
return await element._adoptTo(await resolved.frame._mainContext());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return continuePolling;
|
return continuePolling;
|
||||||
}
|
}
|
||||||
}, scope);
|
});
|
||||||
|
return scope ? scope._context._raceAgainstContextDestroyed(promise) : promise;
|
||||||
}, this._page._timeoutSettings.timeout(options));
|
}, this._page._timeoutSettings.timeout(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1050,45 +1066,6 @@ export class Frame extends SdkObject {
|
||||||
return result!;
|
return result!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async retryWithProgress<R>(
|
|
||||||
progress: Progress,
|
|
||||||
selector: string,
|
|
||||||
options: types.StrictOptions & types.TimeoutOptions & { omitAttached?: boolean },
|
|
||||||
action: (selector: SelectorInFrame | null, continuePolling: symbol) => Promise<R | symbol>,
|
|
||||||
scope?: dom.ElementHandle): Promise<R> {
|
|
||||||
const continuePolling = Symbol('continuePolling');
|
|
||||||
while (progress.isRunning()) {
|
|
||||||
let selectorInFrame: SelectorInFrame | null;
|
|
||||||
if (options.omitAttached) {
|
|
||||||
selectorInFrame = await this.resolveFrameForSelectorNoWait(selector, options, scope);
|
|
||||||
} else {
|
|
||||||
selectorInFrame = await this._resolveFrameForSelector(progress, selector, options, scope);
|
|
||||||
if (!selectorInFrame) {
|
|
||||||
// Missing content frame.
|
|
||||||
await new Promise(f => setTimeout(f, 100));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const result = await action(selectorInFrame, continuePolling);
|
|
||||||
if (result === continuePolling)
|
|
||||||
continue;
|
|
||||||
return result as R;
|
|
||||||
} catch (e) {
|
|
||||||
if (this._isErrorThatCannotBeRetried(e))
|
|
||||||
throw e;
|
|
||||||
// If there is scope, and scope is within the frame we use to select, assume context is destroyed and
|
|
||||||
// operation is not recoverable.
|
|
||||||
if (scope && scope._context.frame === selectorInFrame?.frame)
|
|
||||||
throw e;
|
|
||||||
// Retry upon all other errors.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
progress.throwIfAborted();
|
|
||||||
return undefined as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
async retryWithProgressAndTimeouts<R>(progress: Progress, timeouts: number[], action: (continuePolling: symbol) => Promise<R | symbol>): Promise<R> {
|
async retryWithProgressAndTimeouts<R>(progress: Progress, timeouts: number[], action: (continuePolling: symbol) => Promise<R | symbol>): Promise<R> {
|
||||||
const continuePolling = Symbol('continuePolling');
|
const continuePolling = Symbol('continuePolling');
|
||||||
timeouts = [0, ...timeouts];
|
timeouts = [0, ...timeouts];
|
||||||
|
|
@ -1135,8 +1112,8 @@ export class Frame extends SdkObject {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _resolveInjectedForSelector(progress: Progress, selector: string, options: { strict?: boolean, mainWorld?: boolean }): Promise<{ injected: js.JSHandle<InjectedScript>, info: SelectorInfo } | undefined> {
|
private async _resolveInjectedForSelector(progress: Progress, selector: string, options: { strict?: boolean, mainWorld?: boolean }, scope?: dom.ElementHandle): Promise<{ injected: js.JSHandle<InjectedScript>, info: SelectorInfo, frame: Frame } | undefined> {
|
||||||
const selectorInFrame = await this.resolveFrameForSelectorNoWait(selector, options);
|
const selectorInFrame = await this.resolveFrameForSelectorNoWait(selector, options, scope);
|
||||||
if (!selectorInFrame)
|
if (!selectorInFrame)
|
||||||
return;
|
return;
|
||||||
progress.throwIfAborted();
|
progress.throwIfAborted();
|
||||||
|
|
@ -1145,7 +1122,7 @@ export class Frame extends SdkObject {
|
||||||
const context = await selectorInFrame.frame._context(options.mainWorld ? 'main' : selectorInFrame.info.world);
|
const context = await selectorInFrame.frame._context(options.mainWorld ? 'main' : selectorInFrame.info.world);
|
||||||
const injected = await context.injectedScript();
|
const injected = await context.injectedScript();
|
||||||
progress.throwIfAborted();
|
progress.throwIfAborted();
|
||||||
return { injected, info: selectorInFrame.info };
|
return { injected, info: selectorInFrame.info, frame: selectorInFrame.frame };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _retryWithProgressIfNotConnected<R>(
|
private async _retryWithProgressIfNotConnected<R>(
|
||||||
|
|
@ -1674,68 +1651,32 @@ export class Frame extends SdkObject {
|
||||||
}, { source, arg });
|
}, { source, arg });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _resolveFrameForSelector(progress: Progress, selector: string, options: types.StrictOptions & types.TimeoutOptions, scope?: dom.ElementHandle): Promise<SelectorInFrame | null> {
|
|
||||||
const elementPath: dom.ElementHandle<Element>[] = [];
|
|
||||||
progress.cleanupWhenAborted(() => {
|
|
||||||
// Do not await here to avoid being blocked, either by stalled
|
|
||||||
// page (e.g. alert) or unresolved navigation in Chromium.
|
|
||||||
for (const element of elementPath)
|
|
||||||
element.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
let frame: Frame | null = this;
|
|
||||||
const frameChunks = splitSelectorByFrame(selector);
|
|
||||||
|
|
||||||
for (let i = 0; i < frameChunks.length - 1 && progress.isRunning(); ++i) {
|
|
||||||
const info = this._page.parseSelector(frameChunks[i], options);
|
|
||||||
const task = dom.waitForSelectorTask(info, 'attached', false, i === 0 ? scope : undefined);
|
|
||||||
progress.log(` waiting for ${this._asLocator(stringifySelector(frameChunks[i]) + ' >> internal:control=enter-frame')}`);
|
|
||||||
const handle = i === 0 && scope ? await frame._runWaitForSelectorTaskOnce(progress, stringifySelector(info.parsed), info.world, task)
|
|
||||||
: await frame._scheduleRerunnableHandleTask(progress, info.world, task);
|
|
||||||
const element = handle.asElement() as dom.ElementHandle<Element>;
|
|
||||||
const isIframe = await element.isIframeElement();
|
|
||||||
if (isIframe === 'error:notconnected')
|
|
||||||
return null; // retry
|
|
||||||
if (!isIframe)
|
|
||||||
throw new Error(`Selector "${stringifySelector(info.parsed)}" resolved to ${element.preview()}, <iframe> was expected`);
|
|
||||||
frame = await element.contentFrame();
|
|
||||||
element.dispose();
|
|
||||||
if (!frame)
|
|
||||||
return null; // retry
|
|
||||||
}
|
|
||||||
return { frame, info: this._page.parseSelector(frameChunks[frameChunks.length - 1], options) };
|
|
||||||
}
|
|
||||||
|
|
||||||
async resolveFrameForSelectorNoWait(selector: string, options: types.StrictOptions = {}, scope?: dom.ElementHandle): Promise<SelectorInFrame | null> {
|
async resolveFrameForSelectorNoWait(selector: string, options: types.StrictOptions = {}, scope?: dom.ElementHandle): Promise<SelectorInFrame | null> {
|
||||||
let frame: Frame | null = this;
|
let frame: Frame = this;
|
||||||
const frameChunks = splitSelectorByFrame(selector);
|
const frameChunks = splitSelectorByFrame(selector);
|
||||||
|
|
||||||
for (let i = 0; i < frameChunks.length - 1; ++i) {
|
for (let i = 0; i < frameChunks.length - 1; ++i) {
|
||||||
const info = this._page.parseSelector(frameChunks[i], options);
|
const info = this._page.parseSelector(frameChunks[i], options);
|
||||||
const element: dom.ElementHandle<Element> | null = await this._page.selectors.query(frame, info, i === 0 ? scope : undefined);
|
const context = await frame._context(info.world);
|
||||||
|
const injectedScript = await context.injectedScript();
|
||||||
|
const handle = await injectedScript.evaluateHandle((injected, { info, scope, selectorString }) => {
|
||||||
|
const element = injected.querySelector(info.parsed, scope || document, info.strict);
|
||||||
|
if (element && element.nodeName !== 'IFRAME' && element.nodeName !== 'FRAME')
|
||||||
|
throw injected.createStacklessError(`Selector "${selectorString}" resolved to ${injected.previewNode(element)}, <iframe> was expected`);
|
||||||
|
return element;
|
||||||
|
}, { info, scope: i === 0 ? scope : undefined, selectorString: stringifySelector(info.parsed) });
|
||||||
|
const element = handle.asElement() as dom.ElementHandle<Element> | null;
|
||||||
if (!element)
|
if (!element)
|
||||||
return null;
|
return null;
|
||||||
frame = await element.contentFrame();
|
const maybeFrame = await this._page._delegate.getContentFrame(element);
|
||||||
element.dispose();
|
element.dispose();
|
||||||
if (!frame)
|
if (!maybeFrame)
|
||||||
throw new Error(`Selector "${stringifySelector(info.parsed)}" resolved to ${element.preview()}, <iframe> was expected`);
|
return null;
|
||||||
|
frame = maybeFrame;
|
||||||
}
|
}
|
||||||
return { frame, info: this._page.parseSelector(frameChunks[frameChunks.length - 1], options) };
|
return { frame, info: this._page.parseSelector(frameChunks[frameChunks.length - 1], options) };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _runWaitForSelectorTaskOnce<T>(progress: Progress, selector: string, world: types.World, task: dom.SchedulableTask<T>): Promise<js.SmartHandle<T>> {
|
|
||||||
const context = await this._context(world);
|
|
||||||
const injected = await context.injectedScript();
|
|
||||||
try {
|
|
||||||
const pollHandler = new dom.InjectedScriptPollHandler(progress, await task(injected));
|
|
||||||
const result = await pollHandler.finishHandle();
|
|
||||||
progress.cleanupWhenAborted(() => result.dispose());
|
|
||||||
return result;
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(`Error: frame navigated while waiting for ${this._asLocator(selector)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetStorageForCurrentOriginBestEffort(newStorage: channels.OriginStorage | undefined) {
|
async resetStorageForCurrentOriginBestEffort(newStorage: channels.OriginStorage | undefined) {
|
||||||
const context = await this._utilityContext();
|
const context = await this._utilityContext();
|
||||||
await context.evaluate(async ({ ls }) => {
|
await context.evaluate(async ({ ls }) => {
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ export class ExecutionContext extends SdkObject {
|
||||||
this._destroyedPromise.resolve(error);
|
this._destroyedPromise.resolve(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _raceAgainstContextDestroyed<T>(promise: Promise<T>): Promise<T> {
|
_raceAgainstContextDestroyed<T>(promise: Promise<T>): Promise<T> {
|
||||||
return Promise.race([
|
return Promise.race([
|
||||||
this._destroyedPromise.then(e => { throw e; }),
|
this._destroyedPromise.then(e => { throw e; }),
|
||||||
promise,
|
promise,
|
||||||
|
|
|
||||||
|
|
@ -474,9 +474,10 @@ test.describe(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('toBeOK fail with promise', async ({ page, server }) => {
|
test('toBeOK fail with promise', async ({ page, server }) => {
|
||||||
const res = page.request.get(server.EMPTY_PAGE).catch(e => {});
|
const res = page.request.get(server.EMPTY_PAGE);
|
||||||
const error = await (expect(res) as any).toBeOK().catch(e => e);
|
const error = await (expect(res) as any).toBeOK().catch(e => e);
|
||||||
expect(error.message).toContain('toBeOK can be only used with APIResponse object');
|
expect(error.message).toContain('toBeOK can be only used with APIResponse object');
|
||||||
|
await res;
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('toBeOK should print response with text content type when fails', () => {
|
test.describe('toBeOK should print response with text content type when fails', () => {
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ it('elementHandle.waitForSelector should throw on navigation', async ({ page, se
|
||||||
await page.evaluate(() => 1);
|
await page.evaluate(() => 1);
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
const error = await promise;
|
const error = await promise;
|
||||||
expect(error.message).toContain('Error: frame navigated while waiting for locator(\'span\')');
|
expect(error.message).toContain(`waiting for locator('span') to be visible`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with removed MutationObserver', async ({ page, server }) => {
|
it('should work with removed MutationObserver', async ({ page, server }) => {
|
||||||
|
|
@ -135,7 +135,6 @@ it('should report logs while waiting for visible', async ({ page, server }) => {
|
||||||
expect(error.message).toContain(`frame.waitForSelector: Timeout 5000ms exceeded.`);
|
expect(error.message).toContain(`frame.waitForSelector: Timeout 5000ms exceeded.`);
|
||||||
expect(error.message).toContain(`waiting for locator(\'div\') to be visible`);
|
expect(error.message).toContain(`waiting for locator(\'div\') to be visible`);
|
||||||
expect(error.message).toContain(`locator resolved to hidden <div id="mydiv" class="foo bar" foo="1234567890123456…>abcdefghijklmnopqrstuvwyxzabcdefghijklmnopqrstuvw…</div>`);
|
expect(error.message).toContain(`locator resolved to hidden <div id="mydiv" class="foo bar" foo="1234567890123456…>abcdefghijklmnopqrstuvwyxzabcdefghijklmnopqrstuvw…</div>`);
|
||||||
expect(error.message).toContain(`locator did not resolve to any element`);
|
|
||||||
expect(error.message).toContain(`locator resolved to hidden <div class="another"></div>`);
|
expect(error.message).toContain(`locator resolved to hidden <div class="another"></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -326,5 +326,5 @@ it('should fail when navigating while on handle', async ({ page, mode, server })
|
||||||
|
|
||||||
const body = await page.waitForSelector('body');
|
const body = await page.waitForSelector('body');
|
||||||
const error = await body.waitForSelector('div', { __testHookBeforeAdoptNode } as any).catch(e => e);
|
const error = await body.waitForSelector('div', { __testHookBeforeAdoptNode } as any).catch(e => e);
|
||||||
expect(error.message).toContain('Error: frame navigated while waiting for locator(\'div\')');
|
expect(error.message).toContain(`waiting for locator('div') to be visible`);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue