Skip to content

Commit

Permalink
api: waitForElement accepts waitFor: attached|detached|visible|hidden (
Browse files Browse the repository at this point in the history
…#1244)

This includes rename waitForSelector -> waitForElement and removes $wait.
  • Loading branch information
dgozman authored Mar 6, 2020
1 parent 9bc6dce commit 1d770af
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 225 deletions.
179 changes: 67 additions & 112 deletions docs/api.md

Large diffs are not rendered by default.

25 changes: 15 additions & 10 deletions src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
return super._createHandle(remoteObject);
}

_injected(): Promise<js.JSHandle> {
_injected(): Promise<js.JSHandle<Injected>> {
const selectors = Selectors._instance();
if (this._injectedPromise && selectors._generation !== this._injectedGeneration) {
this._injectedPromise.then(handle => handle.dispose());
Expand Down Expand Up @@ -456,17 +456,22 @@ export function waitForFunctionTask(selector: string | undefined, pageFunction:
}, await context._injected(), selector, predicateBody, polling, options.timeout || 0, ...args);
}

export function waitForSelectorTask(selector: string, visibility: types.Visibility, timeout: number): Task {
return async (context: FrameExecutionContext) => context.evaluateHandle((injected: Injected, selector: string, visibility: types.Visibility, timeout: number) => {
const polling = visibility === 'any' ? 'mutation' : 'raf';
export function waitForSelectorTask(selector: string, waitFor: 'attached' | 'detached' | 'visible' | 'hidden', timeout: number): Task {
return async (context: FrameExecutionContext) => context.evaluateHandle((injected, selector, waitFor, timeout) => {
const polling = (waitFor === 'attached' || waitFor === 'detached') ? 'mutation' : 'raf';
return injected.poll(polling, selector, timeout, (element: Element | undefined): Element | boolean => {
if (!element)
return visibility === 'hidden';
if (visibility === 'any')
return element;
return injected.isVisible(element) === (visibility === 'visible') ? element : false;
switch (waitFor) {
case 'attached':
return element || false;
case 'detached':
return !element;
case 'visible':
return element && injected.isVisible(element) ? element : false;
case 'hidden':
return !element || !injected.isVisible(element);
}
});
}, await context._injected(), selector, visibility, timeout);
}, await context._injected(), selector, waitFor, timeout);
}

export const setFileInputFunction = async (element: HTMLInputElement, payloads: types.FilePayload[]) => {
Expand Down
42 changes: 18 additions & 24 deletions src/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -589,9 +589,10 @@ export class Frame {
return handle;
}

async waitForSelector(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> {
const { timeout = this._page._timeoutSettings.timeout(), visibility = 'any' } = (options || {});
const handle = await this._waitForSelectorInUtilityContext(selector, visibility, timeout);
async waitForElement(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> {
if (options && (options as any).visibility)
throw new Error('options.visibility is not supported, did you mean options.waitFor?');
const handle = await this._waitForSelectorInUtilityContext(selector, options);
const mainContext = await this._mainContext();
if (handle && handle._context !== mainContext) {
const adopted = this._page._delegate.adoptElementHandle(handle, mainContext);
Expand All @@ -601,10 +602,6 @@ export class Frame {
return handle;
}

async $wait(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> {
return this.waitForSelector(selector, options);
}

$eval: types.$Eval = async (selector, pageFunction, ...args) => {
const context = await this._mainContext();
const elementHandle = await context._$(selector);
Expand Down Expand Up @@ -875,9 +872,9 @@ export class Frame {
handle.dispose();
}

async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: types.WaitForFunctionOptions & { visibility?: types.Visibility } = {}, ...args: any[]): Promise<js.JSHandle | null> {
async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: types.WaitForFunctionOptions & types.WaitForElementOptions = {}, ...args: any[]): Promise<js.JSHandle | null> {
if (helper.isString(selectorOrFunctionOrTimeout))
return this.waitForSelector(selectorOrFunctionOrTimeout, options) as any;
return this.waitForElement(selectorOrFunctionOrTimeout, options) as any;
if (helper.isNumber(selectorOrFunctionOrTimeout))
return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout));
if (typeof selectorOrFunctionOrTimeout === 'function')
Expand All @@ -891,9 +888,9 @@ export class Frame {
throw new Error('waitFor option should be a boolean, got "' + (typeof waitFor) + '"');
let handle: dom.ElementHandle<Element>;
if (waitFor) {
const maybeHandle = await this._waitForSelectorInUtilityContext(selector, 'any', timeout);
const maybeHandle = await this._waitForSelectorInUtilityContext(selector, { timeout, waitFor: 'attached' });
if (!maybeHandle)
throw new Error('No node found for selector: ' + selectorToString(selector, 'any'));
throw new Error('No node found for selector: ' + selectorToString(selector, 'attached'));
handle = maybeHandle;
} else {
const context = await this._context('utility');
Expand All @@ -904,14 +901,12 @@ export class Frame {
return handle;
}

private async _waitForSelectorInUtilityContext(selector: string, waitFor: types.Visibility, timeout: number): Promise<dom.ElementHandle<Element> | null> {
let visibility: types.Visibility = 'any';
if (waitFor === 'visible' || waitFor === 'hidden' || waitFor === 'any')
visibility = waitFor;
else
throw new Error(`Unsupported visibility option "${waitFor}"`);
const task = dom.waitForSelectorTask(selector, visibility, timeout);
const result = await this._scheduleRerunnableTask(task, 'utility', timeout, `selector "${selectorToString(selector, visibility)}"`);
private async _waitForSelectorInUtilityContext(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> {
const { timeout = this._page._timeoutSettings.timeout(), waitFor = 'attached' } = (options || {});
if (!['attached', 'detached', 'visible', 'hidden'].includes(waitFor))
throw new Error(`Unsupported waitFor option "${waitFor}"`);
const task = dom.waitForSelectorTask(selector, waitFor, timeout);
const result = await this._scheduleRerunnableTask(task, 'utility', timeout, `selector "${selectorToString(selector, waitFor)}"`);
if (!result.asElement()) {
result.dispose();
return null;
Expand Down Expand Up @@ -1095,14 +1090,13 @@ function createTimeoutPromise(timeout: number): Disposable<Promise<TimeoutError>
};
}

function selectorToString(selector: string, visibility: types.Visibility): string {
function selectorToString(selector: string, waitFor: 'attached' | 'detached' | 'visible' | 'hidden'): string {
let label;
switch (visibility) {
switch (waitFor) {
case 'visible': label = '[visible] '; break;
case 'hidden': label = '[hidden] '; break;
case 'any':
case undefined:
label = ''; break;
case 'attached': label = ''; break;
case 'detached': label = '[detached]'; break;
}
return `${label}${selector}`;
}
Expand Down
10 changes: 3 additions & 7 deletions src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,8 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().$(selector);
}

async waitForSelector(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> {
return this.mainFrame().waitForSelector(selector, options);
}

async $wait(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> {
return this.mainFrame().$wait(selector, options);
async waitForElement(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> {
return this.mainFrame().waitForElement(selector, options);
}

evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => {
Expand Down Expand Up @@ -483,7 +479,7 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().uncheck(selector, options);
}

async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options?: types.WaitForFunctionOptions & { visibility?: types.Visibility }, ...args: any[]): Promise<js.JSHandle | null> {
async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options?: types.WaitForFunctionOptions & types.WaitForElementOptions, ...args: any[]): Promise<js.JSHandle | null> {
return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
}

Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export type Quad = [ Point, Point, Point, Point ];
export type TimeoutOptions = { timeout?: number };
export type WaitForOptions = TimeoutOptions & { waitFor?: boolean };

export type Visibility = 'visible' | 'hidden' | 'any';
export type WaitForElementOptions = TimeoutOptions & { waitFor?: 'attached' | 'detached' | 'visible' | 'hidden' };

export type Polling = 'raf' | 'mutation' | number;
export type WaitForFunctionOptions = TimeoutOptions & { polling?: Polling };
Expand Down
2 changes: 1 addition & 1 deletion test/chromium/oopif.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
document.body.appendChild(frame);
return new Promise(x => frame.onload = x);
});
await page.waitForSelector('iframe[src="https://google.com/"]');
await page.waitForElement('iframe[src="https://google.com/"]');
const urls = page.frames().map(frame => frame.url()).sort();
expect(urls).toEqual([
server.EMPTY_PAGE,
Expand Down
8 changes: 4 additions & 4 deletions test/launcher.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,15 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
expect(error.message).toContain('Navigation failed because browser has disconnected!');
await browserServer.close();
});
it('should reject waitForSelector when browser closes', async({server}) => {
it('should reject waitForElement when browser closes', async({server}) => {
server.setRoute('/empty.html', () => {});
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e);
const watchdog = page.waitForElement('div', { timeout: 60000 }).catch(e => e);

// Make sure the previous waitForSelector has time to make it to the browser before we disconnect.
await page.waitForSelector('body');
// Make sure the previous waitForElement has time to make it to the browser before we disconnect.
await page.waitForElement('body');

await remote.close();
const error = await watchdog;
Expand Down
11 changes: 0 additions & 11 deletions test/queryselector.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,17 +211,6 @@ module.exports.describe = function({testRunner, expect, selectors, FFOX, CHROMIU
const element = await page.$('css=section >> css=div');
expect(element).toBeTruthy();
});
it('should respect waitFor visibility', async({page, server}) => {
await page.setContent('<section id="testAttribute">43543</section>');
expect(await page.waitForSelector('css=section', { waitFor: 'visible'})).toBeTruthy();
expect(await page.waitForSelector('css=section', { waitFor: 'any'})).toBeTruthy();
expect(await page.waitForSelector('css=section')).toBeTruthy();

await page.setContent('<section id="testAttribute" style="display: none">43543</section>');
expect(await page.waitForSelector('css=section', { waitFor: 'hidden'})).toBeTruthy();
expect(await page.waitForSelector('css=section', { waitFor: 'any'})).toBeTruthy();
expect(await page.waitForSelector('css=section')).toBeTruthy();
});
});

describe('Page.$$', function() {
Expand Down
Loading

0 comments on commit 1d770af

Please sign in to comment.