chore(frame-selector): add more tests, use frame logic in element handle (#10097)
This commit is contained in:
parent
975a00ab31
commit
f3fd3ebc37
|
|
@ -177,8 +177,12 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async isIframeElement(): Promise<boolean | 'error:notconnected'> {
|
||||||
|
return this.evaluateInUtility(([injected, node]) => node && (node.nodeName === 'IFRAME' || node.nodeName === 'FRAME'), {});
|
||||||
|
}
|
||||||
|
|
||||||
async contentFrame(): Promise<frames.Frame | null> {
|
async contentFrame(): Promise<frames.Frame | null> {
|
||||||
const isFrameElement = throwRetargetableDOMError(await this.evaluateInUtility(([injected, node]) => node && (node.nodeName === 'IFRAME' || node.nodeName === 'FRAME'), {}));
|
const isFrameElement = throwRetargetableDOMError(await this.isIframeElement());
|
||||||
if (!isFrameElement)
|
if (!isFrameElement)
|
||||||
return null;
|
return null;
|
||||||
return this._page._delegate.getContentFrame(this);
|
return this._page._delegate.getContentFrame(this);
|
||||||
|
|
@ -692,21 +696,27 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async querySelector(selector: string, options: types.StrictOptions): Promise<ElementHandle | null> {
|
async querySelector(selector: string, options: types.StrictOptions): Promise<ElementHandle | null> {
|
||||||
const { frame, info } = await this._frame.resolveFrameForSelectorNoWait(selector, options, this);
|
const pair = await this._frame.resolveFrameForSelectorNoWait(selector, options, this);
|
||||||
|
if (!pair)
|
||||||
|
return null;
|
||||||
|
const { frame, info } = pair;
|
||||||
// If we end up in the same frame => use the scope again, line above was noop.
|
// If we end up in the same frame => use the scope again, line above was noop.
|
||||||
return this._page.selectors.query(frame, info, this._frame === frame ? this : undefined);
|
return this._page.selectors.query(frame, info, this._frame === frame ? this : undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
async querySelectorAll(selector: string): Promise<ElementHandle<Element>[]> {
|
async querySelectorAll(selector: string): Promise<ElementHandle<Element>[]> {
|
||||||
const { frame, info } = await this._frame.resolveFrameForSelectorNoWait(selector, {}, this);
|
const pair = await this._frame.resolveFrameForSelectorNoWait(selector, {}, this);
|
||||||
|
if (!pair)
|
||||||
|
return [];
|
||||||
|
const { frame, info } = pair;
|
||||||
// If we end up in the same frame => use the scope again, line above was noop.
|
// If we end up in the same frame => use the scope again, line above was noop.
|
||||||
return this._page.selectors._queryAll(frame, info, this._frame === frame ? this : undefined, true /* adoptToMain */);
|
return this._page.selectors._queryAll(frame, info, this._frame === frame ? this : undefined, true /* adoptToMain */);
|
||||||
}
|
}
|
||||||
|
|
||||||
async evalOnSelectorAndWaitForSignals(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
async evalOnSelectorAndWaitForSignals(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
||||||
const { frame, info } = await this._frame.resolveFrameForSelectorNoWait(selector, { strict }, this);
|
const pair = await this._frame.resolveFrameForSelectorNoWait(selector, { strict }, this);
|
||||||
// If we end up in the same frame => use the scope again, line above was noop.
|
// If we end up in the same frame => use the scope again, line above was noop.
|
||||||
const handle = await this._page.selectors.query(frame, info, this._frame === frame ? this : undefined);
|
const handle = pair ? await this._page.selectors.query(pair.frame, pair.info, this._frame === pair.frame ? this : undefined) : null;
|
||||||
if (!handle)
|
if (!handle)
|
||||||
throw new Error(`Error: failed to find element matching selector "${selector}"`);
|
throw new Error(`Error: failed to find element matching selector "${selector}"`);
|
||||||
const result = await handle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg);
|
const result = await handle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg);
|
||||||
|
|
@ -715,7 +725,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async evalOnSelectorAllAndWaitForSignals(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
async evalOnSelectorAllAndWaitForSignals(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
||||||
const { frame, info } = await this._frame.resolveFrameForSelectorNoWait(selector, {}, this);
|
const pair = await this._frame.resolveFrameForSelectorNoWait(selector, {}, this);
|
||||||
|
if (!pair)
|
||||||
|
throw new Error(`Error: failed to find frame for selector "${selector}"`);
|
||||||
|
const { frame, info } = pair;
|
||||||
// If we end up in the same frame => use the scope again, line above was noop.
|
// If we end up in the same frame => use the scope again, line above was noop.
|
||||||
const arrayHandle = await this._page.selectors._queryArray(frame, info, this._frame === frame ? this : undefined);
|
const arrayHandle = await this._page.selectors._queryArray(frame, info, this._frame === frame ? this : undefined);
|
||||||
const result = await arrayHandle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg);
|
const result = await arrayHandle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg);
|
||||||
|
|
@ -767,31 +780,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForSelector(metadata: CallMetadata, selector: string, options: types.WaitForElementOptions = {}): Promise<ElementHandle<Element> | null> {
|
async waitForSelector(metadata: CallMetadata, selector: string, options: types.WaitForElementOptions = {}): Promise<ElementHandle<Element> | null> {
|
||||||
const { state = 'visible' } = options;
|
return this._frame.waitForSelector(metadata, selector, options, this);
|
||||||
if (!['attached', 'detached', 'visible', 'hidden'].includes(state))
|
|
||||||
throw new Error(`state: expected one of (attached|detached|visible|hidden)`);
|
|
||||||
const controller = new ProgressController(metadata, this);
|
|
||||||
return controller.run(async progress => {
|
|
||||||
progress.log(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`);
|
|
||||||
return this._frame.retryWithProgress(progress, selector, options, async (frame, info, continuePolling) => {
|
|
||||||
// If we end up in the same frame => use the scope again, line above was noop.
|
|
||||||
const task = waitForSelectorTask(info, state, false, frame === this._frame ? this : undefined);
|
|
||||||
const context = await frame._context(info.world);
|
|
||||||
const injected = await context.injectedScript();
|
|
||||||
const pollHandler = new InjectedScriptPollHandler(progress, await task(injected));
|
|
||||||
const result = await pollHandler.finishHandle();
|
|
||||||
if (!result.asElement()) {
|
|
||||||
result.dispose();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const handle = result.asElement() as ElementHandle<Element>;
|
|
||||||
try {
|
|
||||||
return await handle._adoptTo(await frame._mainContext());
|
|
||||||
} catch (e) {
|
|
||||||
return continuePolling;
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
}, this._page._timeoutSettings.timeout(options));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _adoptTo(context: FrameExecutionContext): Promise<ElementHandle<T>> {
|
async _adoptTo(context: FrameExecutionContext): Promise<ElementHandle<T>> {
|
||||||
|
|
|
||||||
|
|
@ -712,11 +712,13 @@ export class Frame extends SdkObject {
|
||||||
|
|
||||||
async querySelector(selector: string, options: types.StrictOptions): Promise<dom.ElementHandle<Element> | null> {
|
async querySelector(selector: string, options: types.StrictOptions): Promise<dom.ElementHandle<Element> | null> {
|
||||||
debugLogger.log('api', ` finding element using the selector "${selector}"`);
|
debugLogger.log('api', ` finding element using the selector "${selector}"`);
|
||||||
const { frame, info } = await this.resolveFrameForSelectorNoWait(selector, options);
|
const result = await this.resolveFrameForSelectorNoWait(selector, options);
|
||||||
return this._page.selectors.query(frame, info);
|
if (!result)
|
||||||
|
return null;
|
||||||
|
return this._page.selectors.query(result.frame, result.info);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForSelector(metadata: CallMetadata, selector: string, options: types.WaitForElementOptions & { omitReturnValue?: boolean } = {}): Promise<dom.ElementHandle<Element> | null> {
|
async waitForSelector(metadata: CallMetadata, selector: string, options: types.WaitForElementOptions & { omitReturnValue?: boolean }, 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?');
|
||||||
|
|
@ -728,8 +730,11 @@ export class Frame extends SdkObject {
|
||||||
return controller.run(async progress => {
|
return controller.run(async progress => {
|
||||||
progress.log(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`);
|
progress.log(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`);
|
||||||
return this.retryWithProgress(progress, selector, options, async (frame, info, continuePolling) => {
|
return this.retryWithProgress(progress, selector, options, async (frame, info, continuePolling) => {
|
||||||
const task = dom.waitForSelectorTask(info, state, options.omitReturnValue);
|
// Be careful, |this| can be different from |frame|.
|
||||||
const result = await frame._scheduleRerunnableHandleTask(progress, info.world, task);
|
const actualScope = this === frame ? scope : undefined;
|
||||||
|
const task = dom.waitForSelectorTask(info, state, options.omitReturnValue, actualScope);
|
||||||
|
const result = actualScope ? await frame._runWaitForSelectorTaskOnce(progress, stringifySelector(info.parsed), info.world, task)
|
||||||
|
: await frame._scheduleRerunnableHandleTask(progress, info.world, task);
|
||||||
if (!result.asElement()) {
|
if (!result.asElement()) {
|
||||||
result.dispose();
|
result.dispose();
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -742,7 +747,7 @@ export class Frame extends SdkObject {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return continuePolling;
|
return continuePolling;
|
||||||
}
|
}
|
||||||
});
|
}, scope);
|
||||||
}, this._page._timeoutSettings.timeout(options));
|
}, this._page._timeoutSettings.timeout(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -754,8 +759,8 @@ export class Frame extends SdkObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
async evalOnSelectorAndWaitForSignals(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
async evalOnSelectorAndWaitForSignals(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
||||||
const { frame, info } = await this.resolveFrameForSelectorNoWait(selector, { strict });
|
const pair = await this.resolveFrameForSelectorNoWait(selector, { strict });
|
||||||
const handle = await this._page.selectors.query(frame, info);
|
const handle = pair ? await this._page.selectors.query(pair.frame, pair.info) : null;
|
||||||
if (!handle)
|
if (!handle)
|
||||||
throw new Error(`Error: failed to find element matching selector "${selector}"`);
|
throw new Error(`Error: failed to find element matching selector "${selector}"`);
|
||||||
const result = await handle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg);
|
const result = await handle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg);
|
||||||
|
|
@ -764,16 +769,20 @@ export class Frame extends SdkObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
async evalOnSelectorAllAndWaitForSignals(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
async evalOnSelectorAllAndWaitForSignals(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
||||||
const { frame, info } = await this.resolveFrameForSelectorNoWait(selector, {});
|
const pair = await this.resolveFrameForSelectorNoWait(selector, {});
|
||||||
const arrayHandle = await this._page.selectors._queryArray(frame, info);
|
if (!pair)
|
||||||
|
throw new Error(`Error: failed to find frame for selector "${selector}"`);
|
||||||
|
const arrayHandle = await this._page.selectors._queryArray(pair.frame, pair.info);
|
||||||
const result = await arrayHandle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg);
|
const result = await arrayHandle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg);
|
||||||
arrayHandle.dispose();
|
arrayHandle.dispose();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async querySelectorAll(selector: string): Promise<dom.ElementHandle<Element>[]> {
|
async querySelectorAll(selector: string): Promise<dom.ElementHandle<Element>[]> {
|
||||||
const { frame, info } = await this.resolveFrameForSelectorNoWait(selector, {});
|
const pair = await this.resolveFrameForSelectorNoWait(selector, {});
|
||||||
return this._page.selectors._queryAll(frame, info, undefined, true /* adoptToMain */);
|
if (!pair)
|
||||||
|
return [];
|
||||||
|
return this._page.selectors._queryAll(pair.frame, pair.info, undefined, true /* adoptToMain */);
|
||||||
}
|
}
|
||||||
|
|
||||||
async content(): Promise<string> {
|
async content(): Promise<string> {
|
||||||
|
|
@ -961,7 +970,13 @@ export class Frame extends SdkObject {
|
||||||
scope?: dom.ElementHandle): Promise<R> {
|
scope?: dom.ElementHandle): Promise<R> {
|
||||||
const continuePolling = Symbol('continuePolling');
|
const continuePolling = Symbol('continuePolling');
|
||||||
while (progress.isRunning()) {
|
while (progress.isRunning()) {
|
||||||
const { frame, info } = await this.resolveFrameForSelector(progress, selector, options, scope);
|
const pair = await this._resolveFrameForSelector(progress, selector, options, scope);
|
||||||
|
if (!pair) {
|
||||||
|
// Missing content frame.
|
||||||
|
await new Promise(f => setTimeout(f, 100));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const { frame, info } = pair;
|
||||||
try {
|
try {
|
||||||
const result = await action(frame, info, continuePolling);
|
const result = await action(frame, info, continuePolling);
|
||||||
if (result === continuePolling)
|
if (result === continuePolling)
|
||||||
|
|
@ -987,6 +1002,7 @@ export class Frame extends SdkObject {
|
||||||
strict: boolean | undefined,
|
strict: boolean | undefined,
|
||||||
action: (handle: dom.ElementHandle<Element>) => Promise<R | 'error:notconnected'>): Promise<R> {
|
action: (handle: dom.ElementHandle<Element>) => Promise<R | 'error:notconnected'>): Promise<R> {
|
||||||
return this.retryWithProgress(progress, selector, { strict }, async (frame, info, continuePolling) => {
|
return this.retryWithProgress(progress, selector, { strict }, async (frame, info, continuePolling) => {
|
||||||
|
// Be careful, |this| can be different from |frame|.
|
||||||
progress.log(`waiting for selector "${selector}"`);
|
progress.log(`waiting for selector "${selector}"`);
|
||||||
const task = dom.waitForSelectorTask(info, 'attached');
|
const task = dom.waitForSelectorTask(info, 'attached');
|
||||||
const handle = await frame._scheduleRerunnableHandleTask(progress, info.world, task);
|
const handle = await frame._scheduleRerunnableHandleTask(progress, info.world, task);
|
||||||
|
|
@ -1106,8 +1122,10 @@ export class Frame extends SdkObject {
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
return controller.run(async progress => {
|
return controller.run(async progress => {
|
||||||
progress.log(` checking visibility of "${selector}"`);
|
progress.log(` checking visibility of "${selector}"`);
|
||||||
const { frame, info } = await this.resolveFrameForSelector(progress, selector, options);
|
const pair = await this.resolveFrameForSelectorNoWait(selector, options);
|
||||||
const element = await this._page.selectors.query(frame, info);
|
if (!pair)
|
||||||
|
return false;
|
||||||
|
const element = await this._page.selectors.query(pair.frame, pair.info);
|
||||||
return element ? await element.isVisible() : false;
|
return element ? await element.isVisible() : false;
|
||||||
}, this._page._timeoutSettings.timeout({}));
|
}, this._page._timeoutSettings.timeout({}));
|
||||||
}
|
}
|
||||||
|
|
@ -1309,6 +1327,7 @@ export class Frame extends SdkObject {
|
||||||
|
|
||||||
return controller.run(async progress => {
|
return controller.run(async progress => {
|
||||||
return this.retryWithProgress(progress, selector, options, async (frame, info) => {
|
return this.retryWithProgress(progress, selector, options, async (frame, info) => {
|
||||||
|
// Be careful, |this| can be different from |frame|.
|
||||||
progress.log(`waiting for selector "${selector}"`);
|
progress.log(`waiting for selector "${selector}"`);
|
||||||
return await frame._scheduleRerunnableTaskInFrame(progress, info, callbackText, taskData, options);
|
return await frame._scheduleRerunnableTaskInFrame(progress, info, callbackText, taskData, options);
|
||||||
});
|
});
|
||||||
|
|
@ -1442,7 +1461,7 @@ export class Frame extends SdkObject {
|
||||||
}, { source, arg });
|
}, { source, arg });
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveFrameForSelector(progress: Progress, selector: string, options: types.StrictOptions & types.TimeoutOptions, scope?: dom.ElementHandle): Promise<{ frame: Frame, info: SelectorInfo }> {
|
private async _resolveFrameForSelector(progress: Progress, selector: string, options: types.StrictOptions & types.TimeoutOptions, scope?: dom.ElementHandle): Promise<{ frame: Frame, info: SelectorInfo } | null> {
|
||||||
const elementPath: dom.ElementHandle<Element>[] = [];
|
const elementPath: dom.ElementHandle<Element>[] = [];
|
||||||
progress.cleanupWhenAborted(() => {
|
progress.cleanupWhenAborted(() => {
|
||||||
// Do not await here to avoid being blocked, either by stalled
|
// Do not await here to avoid being blocked, either by stalled
|
||||||
|
|
@ -1457,8 +1476,31 @@ export class Frame extends SdkObject {
|
||||||
for (let i = 0; i < frameChunks.length - 1 && progress.isRunning(); ++i) {
|
for (let i = 0; i < frameChunks.length - 1 && progress.isRunning(); ++i) {
|
||||||
const info = this._page.parseSelector(frameChunks[i], options);
|
const info = this._page.parseSelector(frameChunks[i], options);
|
||||||
const task = dom.waitForSelectorTask(info, 'attached', false, i === 0 ? scope : undefined);
|
const task = dom.waitForSelectorTask(info, 'attached', false, i === 0 ? scope : undefined);
|
||||||
const handle = await frame._scheduleRerunnableHandleTask(progress, info.world, task);
|
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 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 & types.TimeoutOptions, scope?: dom.ElementHandle): Promise<{ frame: Frame, info: SelectorInfo } | null> {
|
||||||
|
let frame: Frame | null = this;
|
||||||
|
const frameChunks = splitSelectorByFrame(selector);
|
||||||
|
|
||||||
|
for (let i = 0; i < frameChunks.length - 1; ++i) {
|
||||||
|
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);
|
||||||
|
if (!element)
|
||||||
|
return null;
|
||||||
frame = await element.contentFrame();
|
frame = await element.contentFrame();
|
||||||
element.dispose();
|
element.dispose();
|
||||||
if (!frame)
|
if (!frame)
|
||||||
|
|
@ -1467,21 +1509,17 @@ export class Frame extends SdkObject {
|
||||||
return { frame, info: this._page.parseSelector(frameChunks[frameChunks.length - 1], options) };
|
return { frame, info: this._page.parseSelector(frameChunks[frameChunks.length - 1], options) };
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveFrameForSelectorNoWait(selector: string, options: types.StrictOptions & types.TimeoutOptions, scope?: dom.ElementHandle): Promise<{ frame: Frame, info: SelectorInfo }> {
|
private async _runWaitForSelectorTaskOnce<T>(progress: Progress, selector: string, world: types.World, task: dom.SchedulableTask<T>): Promise<js.SmartHandle<T>> {
|
||||||
let frame: Frame | null = this;
|
const context = await this._context(world);
|
||||||
const frameChunks = splitSelectorByFrame(selector);
|
const injected = await context.injectedScript();
|
||||||
|
try {
|
||||||
for (let i = 0; i < frameChunks.length - 1; ++i) {
|
const pollHandler = new dom.InjectedScriptPollHandler(progress, await task(injected));
|
||||||
const info = this._page.parseSelector(frameChunks[i], options);
|
const result = await pollHandler.finishHandle();
|
||||||
const element: dom.ElementHandle<Element> | null = await this._page.selectors.query(frame, info, i === 0 ? scope : undefined);
|
progress.cleanupWhenAborted(() => result.dispose());
|
||||||
if (!element)
|
return result;
|
||||||
throw new Error(`Could not find frame while resolving "${stringifySelector(info.parsed)}"`);
|
} catch (e) {
|
||||||
frame = await element.contentFrame();
|
throw new Error(`Error: frame navigated while waiting for selector "${selector}"`);
|
||||||
element.dispose();
|
|
||||||
if (!frame)
|
|
||||||
throw new Error(`Selector "${stringifySelector(info.parsed)}" resolved to ${element.preview()}, <iframe> was expected`);
|
|
||||||
}
|
}
|
||||||
return { frame, info: this._page.parseSelector(frameChunks[frameChunks.length - 1], options) };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1508,6 +1546,7 @@ class RerunnableTask<T> {
|
||||||
terminate(error: Error) {
|
terminate(error: Error) {
|
||||||
this._reject(error);
|
this._reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _resolve(value: T | js.SmartHandle<T>) {
|
private _resolve(value: T | js.SmartHandle<T>) {
|
||||||
if (this.promise)
|
if (this.promise)
|
||||||
this.promise.resolve(value as T);
|
this.promise.resolve(value as T);
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,8 @@ 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('Execution context was destroyed');
|
expect(error.message).toContain('Error: frame navigated while waiting for selector');
|
||||||
|
expect(error.message).toContain('span');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with removed MutationObserver', async ({ page, server }) => {
|
it('should work with removed MutationObserver', async ({ page, server }) => {
|
||||||
|
|
|
||||||
|
|
@ -288,3 +288,23 @@ it('should work when navigating before node adoption', async ({ page, mode, serv
|
||||||
// This text is coming from /one-style.html
|
// This text is coming from /one-style.html
|
||||||
expect(await div.textContent()).toBe('hello, world!');
|
expect(await div.textContent()).toBe('hello, world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fail when navigating while on handle', async ({ page, mode, server }) => {
|
||||||
|
it.skip(mode !== 'default');
|
||||||
|
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
await page.setContent(`<div>Hello</div>`);
|
||||||
|
|
||||||
|
let navigatedOnce = false;
|
||||||
|
const __testHookBeforeAdoptNode = async () => {
|
||||||
|
if (!navigatedOnce) {
|
||||||
|
navigatedOnce = true;
|
||||||
|
await page.goto(server.PREFIX + '/one-style.html');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const body = await page.waitForSelector('body');
|
||||||
|
const error = await body.waitForSelector('div', { __testHookBeforeAdoptNode } as any).catch(e => e);
|
||||||
|
expect(error.message).toContain('Error: frame navigated while waiting for selector');
|
||||||
|
expect(error.message).toContain('"div"');
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,46 @@ it('should work for $ and $$', async ({ page, server }) => {
|
||||||
expect(elements).toHaveLength(2);
|
expect(elements).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('$ should not wait for frame', async ({ page, server }) => {
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
expect(await page.$('iframe >> content-frame=true >> canvas')).toBeFalsy();
|
||||||
|
const body = await page.$('body');
|
||||||
|
expect(await body.$('iframe >> content-frame=true >> canvas')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('$$ should not wait for frame', async ({ page, server }) => {
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
expect(await page.$$('iframe >> content-frame=true >> canvas')).toHaveLength(0);
|
||||||
|
const body = await page.$('body');
|
||||||
|
expect(await body.$$('iframe >> content-frame=true >> canvas')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('$eval should throw for missing frame', async ({ page, server }) => {
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
{
|
||||||
|
const error = await page.$eval('iframe >> content-frame=true >> canvas', e => 1).catch(e => e);
|
||||||
|
expect(error.message).toContain('Error: failed to find element matching selector');
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const body = await page.$('body');
|
||||||
|
const error = await body.$eval('iframe >> content-frame=true >> canvas', e => 1).catch(e => e);
|
||||||
|
expect(error.message).toContain('Error: failed to find element matching selector');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('$$eval should throw for missing frame', async ({ page, server }) => {
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
{
|
||||||
|
const error = await page.$$eval('iframe >> content-frame=true >> canvas', e => 1).catch(e => e);
|
||||||
|
expect(error.message).toContain('Error: failed to find frame for selector');
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const body = await page.$('body');
|
||||||
|
const error = await body.$$eval('iframe >> content-frame=true >> canvas', e => 1).catch(e => e);
|
||||||
|
expect(error.message).toContain('Error: failed to find frame for selector');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('should work for $ and $$ (handle)', async ({ page, server }) => {
|
it('should work for $ and $$ (handle)', async ({ page, server }) => {
|
||||||
await routeIframe(page);
|
await routeIframe(page);
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
|
@ -213,6 +253,29 @@ it('waitFor should survive frame reattach', async ({ page, server }) => {
|
||||||
await promise;
|
await promise;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('waitForSelector should survive frame reattach (handle)', async ({ page, server }) => {
|
||||||
|
await routeIframe(page);
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
const body = await page.$('body');
|
||||||
|
const promise = body.waitForSelector('iframe >> content-frame=true >> button:has-text("Hello nested iframe")');
|
||||||
|
await page.locator('iframe').evaluate(e => e.remove());
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.src = 'iframe-2.html';
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
});
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('waitForSelector should survive iframe navigation (handle)', async ({ page, server }) => {
|
||||||
|
await routeIframe(page);
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
const body = await page.$('body');
|
||||||
|
const promise = body.waitForSelector('iframe >> content-frame=true >> button:has-text("Hello nested iframe")');
|
||||||
|
page.locator('iframe').evaluate(e => (e as HTMLIFrameElement).src = 'iframe-2.html');
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
|
|
||||||
it('click should survive frame reattach', async ({ page, server }) => {
|
it('click should survive frame reattach', async ({ page, server }) => {
|
||||||
await routeIframe(page);
|
await routeIframe(page);
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
|
@ -255,3 +318,12 @@ it('should fail if element removed while waiting on element handle', async ({ pa
|
||||||
await page.evaluate(() => document.body.innerText = '');
|
await page.evaluate(() => document.body.innerText = '');
|
||||||
await promise;
|
await promise;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should non work for non-frame', async ({ page, server }) => {
|
||||||
|
await routeIframe(page);
|
||||||
|
await page.setContent('<div></div>');
|
||||||
|
const button = page.locator('div >> content-frame=true >> button');
|
||||||
|
const error = await button.waitFor().catch(e => e);
|
||||||
|
expect(error.message).toContain('<div></div>');
|
||||||
|
expect(error.message).toContain('<iframe> was expected');
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue